Merge pull request #8617 from MrPetovan/task/8220-twitter-followers-list
(Re)Implement Twitter contact API endpoints
This commit is contained in:
commit
f10062dfdb
|
@ -152,19 +152,29 @@ These endpoints use the [Friendica API entities](help/API-Entities).
|
||||||
- [GET api/friendships/incoming](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming)
|
- [GET api/friendships/incoming](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming)
|
||||||
- Unsupported parameters
|
- Unsupported parameters
|
||||||
- `stringify_ids`
|
- `stringify_ids`
|
||||||
- [GET api/followers/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids)
|
|
||||||
- Unsupported parameters:
|
- - [GET api/followers/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids)
|
||||||
- `user_id`: Relationships aren't returned for other users than self
|
- [GET api/followers/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list)
|
||||||
- `screen_name`: Relationships aren't returned for other users than self
|
- [GET api/friends/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids)
|
||||||
- [GET api/friends/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids)
|
- [GET api/friends/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list)
|
||||||
- Unsupported parameters:
|
- Additional parameters:
|
||||||
- `user_id`: Relationships aren't returned for other users than self
|
- `since_id`: You can use the `next_cursor` value to load the next page.
|
||||||
- `screen_name`: Relationships aren't returned for other users than self
|
- `max_id`: You can use the inverse of the `previous_cursor` value to load the previous page.
|
||||||
|
- Unsupported parameter:
|
||||||
|
- `skip_status`: No status is returned even if it isn't set to true.
|
||||||
|
- Caveats:
|
||||||
|
- `cursor` trumps `since_id` trumps `max_id` if any combination is provided.
|
||||||
|
- `user_id` must be the ID of a contact associated with a local user account.
|
||||||
|
- `screen_name` must be associated with a local user account.
|
||||||
|
- `screen_name` trumps `user_id` if both are provided (undocumented Twitter behavior).
|
||||||
|
- Will succeed but return an empty array for users hiding their contact lists.
|
||||||
|
|
||||||
|
|
||||||
- [POST api/friendships/destroy](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy)
|
- [POST api/friendships/destroy](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Non-implemented endpoints
|
## Non-implemented endpoints
|
||||||
|
|
||||||
- [GET oauth/authenticate](https://developer.twitter.com/en/docs/basics/authentication/api-reference/authenticate)
|
- [GET oauth/authenticate](https://developer.twitter.com/en/docs/basics/authentication/api-reference/authenticate)
|
||||||
|
@ -188,8 +198,6 @@ These endpoints use the [Friendica API entities](help/API-Entities).
|
||||||
- [POST lists/subscribers/destroy](https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-destroy)
|
- [POST lists/subscribers/destroy](https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-destroy)
|
||||||
|
|
||||||
|
|
||||||
- [GET followers/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list)
|
|
||||||
- [GET friends/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list)
|
|
||||||
- [GET friendships/lookup](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup)
|
- [GET friendships/lookup](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup)
|
||||||
- [GET friendships/no_retweets/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-no_retweets-ids)
|
- [GET friendships/no_retweets/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-no_retweets-ids)
|
||||||
- [GET friendships/outgoing](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-outgoing)
|
- [GET friendships/outgoing](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-outgoing)
|
||||||
|
|
|
@ -3580,96 +3580,6 @@ function api_statusnet_version($type)
|
||||||
api_register_func('api/gnusocial/version', 'api_statusnet_version', false);
|
api_register_func('api/gnusocial/version', 'api_statusnet_version', false);
|
||||||
api_register_func('api/statusnet/version', 'api_statusnet_version', false);
|
api_register_func('api/statusnet/version', 'api_statusnet_version', false);
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param string $type Return type (atom, rss, xml, json)
|
|
||||||
*
|
|
||||||
* @param int $rel A contact relationship constant
|
|
||||||
* @return array|string|void
|
|
||||||
* @throws BadRequestException
|
|
||||||
* @throws ForbiddenException
|
|
||||||
* @throws ImagickException
|
|
||||||
* @throws InternalServerErrorException
|
|
||||||
* @throws UnauthorizedException
|
|
||||||
* @todo use api_format_data() to return data
|
|
||||||
*/
|
|
||||||
function api_ff_ids($type, int $rel)
|
|
||||||
{
|
|
||||||
if (!api_user()) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$a = DI::app();
|
|
||||||
|
|
||||||
api_get_user($a);
|
|
||||||
|
|
||||||
$stringify_ids = $_REQUEST['stringify_ids'] ?? false;
|
|
||||||
|
|
||||||
$contacts = DBA::p("SELECT `pcontact`.`id`
|
|
||||||
FROM `contact`
|
|
||||||
INNER JOIN `contact` AS `pcontact`
|
|
||||||
ON `contact`.`nurl` = `pcontact`.`nurl`
|
|
||||||
AND `pcontact`.`uid` = 0
|
|
||||||
WHERE `contact`.`uid` = ?
|
|
||||||
AND NOT `contact`.`self`
|
|
||||||
AND `contact`.`rel` IN (?, ?)",
|
|
||||||
api_user(),
|
|
||||||
$rel,
|
|
||||||
Contact::FRIEND
|
|
||||||
);
|
|
||||||
|
|
||||||
$ids = [];
|
|
||||||
foreach (DBA::toArray($contacts) as $contact) {
|
|
||||||
if ($stringify_ids) {
|
|
||||||
$ids[] = $contact['id'];
|
|
||||||
} else {
|
|
||||||
$ids[] = intval($contact['id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return api_format_data('ids', $type, ['id' => $ids]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ID of every user the user is following.
|
|
||||||
*
|
|
||||||
* @param string $type Return type (atom, rss, xml, json)
|
|
||||||
*
|
|
||||||
* @return array|string
|
|
||||||
* @throws BadRequestException
|
|
||||||
* @throws ForbiddenException
|
|
||||||
* @throws ImagickException
|
|
||||||
* @throws InternalServerErrorException
|
|
||||||
* @throws UnauthorizedException
|
|
||||||
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
|
|
||||||
*/
|
|
||||||
function api_friends_ids($type)
|
|
||||||
{
|
|
||||||
return api_ff_ids($type, Contact::SHARING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ID of every user following the user.
|
|
||||||
*
|
|
||||||
* @param string $type Return type (atom, rss, xml, json)
|
|
||||||
*
|
|
||||||
* @return array|string
|
|
||||||
* @throws BadRequestException
|
|
||||||
* @throws ForbiddenException
|
|
||||||
* @throws ImagickException
|
|
||||||
* @throws InternalServerErrorException
|
|
||||||
* @throws UnauthorizedException
|
|
||||||
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
|
|
||||||
*/
|
|
||||||
function api_followers_ids($type)
|
|
||||||
{
|
|
||||||
return api_ff_ids($type, Contact::FOLLOWER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @TODO move to top of file or somewhere better
|
|
||||||
api_register_func('api/friends/ids', 'api_friends_ids', true);
|
|
||||||
api_register_func('api/followers/ids', 'api_followers_ids', true);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a new direct message.
|
* Sends a new direct message.
|
||||||
*
|
*
|
||||||
|
|
|
@ -279,6 +279,14 @@ abstract class DI
|
||||||
return self::$dice->create(Factory\Api\Mastodon\Relationship::class);
|
return self::$dice->create(Factory\Api\Mastodon\Relationship::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Factory\Api\Twitter\User
|
||||||
|
*/
|
||||||
|
public static function twitterUser()
|
||||||
|
{
|
||||||
|
return self::$dice->create(Factory\Api\Twitter\User::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Factory\Notification\Notification
|
* @return Factory\Notification\Notification
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2020, Friendica
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Factory\Api\Twitter;
|
||||||
|
|
||||||
|
use Friendica\BaseFactory;
|
||||||
|
use Friendica\Model\APContact;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Network\HTTPException;
|
||||||
|
|
||||||
|
class User extends BaseFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param int $contactId
|
||||||
|
* @param int $uid Public contact (=0) or owner user id
|
||||||
|
* @param bool $skip_status
|
||||||
|
* @param bool $include_user_entities
|
||||||
|
* @return \Friendica\Object\Api\Twitter\User
|
||||||
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
* @throws \ImagickException
|
||||||
|
*/
|
||||||
|
public function createFromContactId(int $contactId, $uid = 0, $skip_status = false, $include_user_entities = true)
|
||||||
|
{
|
||||||
|
$cdata = Contact::getPublicAndUserContacID($contactId, $uid);
|
||||||
|
if (!empty($cdata)) {
|
||||||
|
$publicContact = Contact::getById($cdata['public']);
|
||||||
|
$userContact = Contact::getById($cdata['user']);
|
||||||
|
} else {
|
||||||
|
$publicContact = Contact::getById($contactId);
|
||||||
|
$userContact = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$apcontact = APContact::getByURL($publicContact['url'], false);
|
||||||
|
|
||||||
|
return new \Friendica\Object\Api\Twitter\User($publicContact, $apcontact, $userContact, $skip_status, $include_user_entities);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2020, Friendica
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Module\Api\Twitter;
|
||||||
|
|
||||||
|
use Friendica\Database\DBA;
|
||||||
|
use Friendica\DI;
|
||||||
|
use Friendica\Model\Profile;
|
||||||
|
use Friendica\Model\User;
|
||||||
|
use Friendica\Module\BaseApi;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Network\HTTPException;
|
||||||
|
use Friendica\Util\Strings;
|
||||||
|
|
||||||
|
abstract class ContactEndpoint extends BaseApi
|
||||||
|
{
|
||||||
|
const DEFAULT_COUNT = 20;
|
||||||
|
const MAX_COUNT = 200;
|
||||||
|
|
||||||
|
public static function init(array $parameters = [])
|
||||||
|
{
|
||||||
|
parent::init($parameters);
|
||||||
|
|
||||||
|
if (!self::login()) {
|
||||||
|
throw new HTTPException\UnauthorizedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the uid from the contact_id + screen_name parameters
|
||||||
|
*
|
||||||
|
* @param int|null $contact_id
|
||||||
|
* @param string $screen_name
|
||||||
|
* @return int
|
||||||
|
* @throws HTTPException\NotFoundException
|
||||||
|
*/
|
||||||
|
protected static function getUid(int $contact_id = null, string $screen_name = null)
|
||||||
|
{
|
||||||
|
$uid = self::$current_user_id;
|
||||||
|
|
||||||
|
if ($contact_id || $screen_name) {
|
||||||
|
// screen_name trumps user_id when both are provided
|
||||||
|
if (!$screen_name) {
|
||||||
|
$contact = Contact::getById($contact_id, ['nick', 'url']);
|
||||||
|
// We don't have the followers of remote accounts so we check for locality
|
||||||
|
if (empty($contact) || !Strings::startsWith($contact['url'], DI::baseUrl()->get())) {
|
||||||
|
throw new HTTPException\NotFoundException(DI::l10n()->t('Contact not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$screen_name = $contact['nick'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::getByNickname($screen_name, ['uid']);
|
||||||
|
if (empty($user)) {
|
||||||
|
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$uid = $user['uid'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This methods expands the contact ids into full user objects in an existing result set.
|
||||||
|
*
|
||||||
|
* @param mixed $rel A relationship constant or a list of them
|
||||||
|
* @param int $uid The local user id we query the contacts from
|
||||||
|
* @param int $cursor
|
||||||
|
* @param int $count
|
||||||
|
* @param bool $skip_status
|
||||||
|
* @param bool $include_user_entities
|
||||||
|
* @return array
|
||||||
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
* @throws HTTPException\NotFoundException
|
||||||
|
* @throws \ImagickException
|
||||||
|
*/
|
||||||
|
protected static function list($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $skip_status = false, bool $include_user_entities = true)
|
||||||
|
{
|
||||||
|
$return = self::ids($rel, $uid, $cursor, $count);
|
||||||
|
|
||||||
|
$users = [];
|
||||||
|
foreach ($return['ids'] as $contactId) {
|
||||||
|
$users[] = DI::twitterUser()->createFromContactId($contactId, $uid, $skip_status, $include_user_entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($return['ids']);
|
||||||
|
$return['users'] = $users;
|
||||||
|
|
||||||
|
$return = [
|
||||||
|
'users' => $users,
|
||||||
|
'next_cursor' => $return['next_cursor'],
|
||||||
|
'next_cursor_str' => $return['next_cursor_str'],
|
||||||
|
'previous_cursor' => $return['previous_cursor'],
|
||||||
|
'previous_cursor_str' => $return['previous_cursor_str'],
|
||||||
|
'total_count' => $return['total_count'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $rel A relationship constant or a list of them
|
||||||
|
* @param int $uid The local user id we query the contacts from
|
||||||
|
* @param int $cursor
|
||||||
|
* @param int $count
|
||||||
|
* @param bool $stringify_ids
|
||||||
|
* @return array
|
||||||
|
* @throws HTTPException\NotFoundException
|
||||||
|
*/
|
||||||
|
protected static function ids($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $stringify_ids = false)
|
||||||
|
{
|
||||||
|
$hide_friends = false;
|
||||||
|
if ($uid != self::$current_user_id) {
|
||||||
|
$profile = Profile::getByUID($uid);
|
||||||
|
if (empty($profile)) {
|
||||||
|
throw new HTTPException\NotFoundException(DI::l10n()->t('Profile not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$hide_friends = (bool)$profile['hide-friends'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = [];
|
||||||
|
$next_cursor = 0;
|
||||||
|
$previous_cursor = 0;
|
||||||
|
$total_count = 0;
|
||||||
|
if (!$hide_friends) {
|
||||||
|
$condition = DBA::collapseCondition([
|
||||||
|
'rel' => $rel,
|
||||||
|
'uid' => $uid,
|
||||||
|
'self' => false,
|
||||||
|
'deleted' => false,
|
||||||
|
'hidden' => false,
|
||||||
|
'archive' => false,
|
||||||
|
'pending' => false
|
||||||
|
]);
|
||||||
|
|
||||||
|
$total_count = DBA::count('contact', $condition);
|
||||||
|
|
||||||
|
if ($cursor !== -1) {
|
||||||
|
if ($cursor > 0) {
|
||||||
|
$condition[0] .= " AND `id` > ?";
|
||||||
|
$condition[] = $cursor;
|
||||||
|
} else {
|
||||||
|
$condition[0] .= " AND `id` < ?";
|
||||||
|
$condition[] = -$cursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$contacts = Contact::selectToArray(['id'], $condition, ['limit' => $count, 'order' => ['id']]);
|
||||||
|
|
||||||
|
// Contains user-specific contact ids
|
||||||
|
$ids = array_column($contacts, 'id');
|
||||||
|
|
||||||
|
// Cursor is on the user-specific contact id since it's the sort field
|
||||||
|
if (count($ids)) {
|
||||||
|
$previous_cursor = -$ids[0];
|
||||||
|
$next_cursor = $ids[count($ids) -1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// No next page
|
||||||
|
if ($total_count <= count($contacts) || count($contacts) < $count) {
|
||||||
|
$next_cursor = 0;
|
||||||
|
}
|
||||||
|
// End of results
|
||||||
|
if ($cursor < 0 && count($contacts) === 0) {
|
||||||
|
$next_cursor = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No previous page
|
||||||
|
if ($cursor === -1) {
|
||||||
|
$previous_cursor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cursor > 0 && count($contacts) === 0) {
|
||||||
|
$previous_cursor = -$cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cursor < 0 && count($contacts) === 0) {
|
||||||
|
$next_cursor = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversion to public contact ids
|
||||||
|
array_walk($ids, function (&$contactId) use ($uid, $stringify_ids) {
|
||||||
|
$cdata = Contact::getPublicAndUserContacID($contactId, $uid);
|
||||||
|
if ($stringify_ids) {
|
||||||
|
$contactId = (string)$cdata['public'];
|
||||||
|
} else {
|
||||||
|
$contactId = (int)$cdata['public'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = [
|
||||||
|
'ids' => $ids,
|
||||||
|
'next_cursor' => $next_cursor,
|
||||||
|
'next_cursor_str' => (string)$next_cursor,
|
||||||
|
'previous_cursor' => $previous_cursor,
|
||||||
|
'previous_cursor_str' => (string)$previous_cursor,
|
||||||
|
'total_count' => $total_count,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2020, Friendica
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Module\Api\Twitter;
|
||||||
|
|
||||||
|
use Friendica\Core\System;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
|
||||||
|
*/
|
||||||
|
class FollowersIds extends ContactEndpoint
|
||||||
|
{
|
||||||
|
public static function rawContent(array $parameters = [])
|
||||||
|
{
|
||||||
|
// Expected value for user_id parameter: public/user contact id
|
||||||
|
$contact_id = filter_input(INPUT_GET, 'user_id' , FILTER_VALIDATE_INT);
|
||||||
|
$screen_name = filter_input(INPUT_GET, 'screen_name');
|
||||||
|
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT);
|
||||||
|
$stringify_ids = filter_input(INPUT_GET, 'stringify_ids', FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
|
||||||
|
'default' => self::DEFAULT_COUNT,
|
||||||
|
'min_range' => 1,
|
||||||
|
'max_range' => self::MAX_COUNT,
|
||||||
|
]]);
|
||||||
|
// Friendica-specific
|
||||||
|
$since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT);
|
||||||
|
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT, ['options' => [
|
||||||
|
'default' => 1,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
System::jsonExit(self::ids(
|
||||||
|
[Contact::FOLLOWER, Contact::FRIEND],
|
||||||
|
self::getUid($contact_id, $screen_name),
|
||||||
|
$cursor ?? $since_id ?? - $max_id,
|
||||||
|
$count,
|
||||||
|
$stringify_ids
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2020, Friendica
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Module\Api\Twitter;
|
||||||
|
|
||||||
|
use Friendica\Core\System;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list
|
||||||
|
*/
|
||||||
|
class FollowersList extends ContactEndpoint
|
||||||
|
{
|
||||||
|
public static function rawContent(array $parameters = [])
|
||||||
|
{
|
||||||
|
// Expected value for user_id parameter: public/user contact id
|
||||||
|
$contact_id = filter_input(INPUT_GET, 'user_id' , FILTER_VALIDATE_INT);
|
||||||
|
$screen_name = filter_input(INPUT_GET, 'screen_name');
|
||||||
|
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT);
|
||||||
|
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
|
||||||
|
'default' => self::DEFAULT_COUNT,
|
||||||
|
'min_range' => 1,
|
||||||
|
'max_range' => self::MAX_COUNT,
|
||||||
|
]]);
|
||||||
|
$skip_status = filter_input(INPUT_GET, 'skip_status' , FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$include_user_entities = filter_input(INPUT_GET, 'include_user_entities', FILTER_VALIDATE_BOOLEAN);
|
||||||
|
|
||||||
|
// Friendica-specific
|
||||||
|
$since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT);
|
||||||
|
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT, ['options' => [
|
||||||
|
'default' => 1,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
|
||||||
|
System::jsonExit(self::list(
|
||||||
|
[Contact::FOLLOWER, Contact::FRIEND],
|
||||||
|
self::getUid($contact_id, $screen_name),
|
||||||
|
$cursor ?? $since_id ?? - $max_id,
|
||||||
|
$count,
|
||||||
|
$skip_status,
|
||||||
|
$include_user_entities
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2020, Friendica
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Module\Api\Twitter;
|
||||||
|
|
||||||
|
use Friendica\Core\System;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
|
||||||
|
*/
|
||||||
|
class FriendsIds extends ContactEndpoint
|
||||||
|
{
|
||||||
|
public static function rawContent(array $parameters = [])
|
||||||
|
{
|
||||||
|
// Expected value for user_id parameter: public/user contact id
|
||||||
|
$contact_id = filter_input(INPUT_GET, 'user_id' , FILTER_VALIDATE_INT);
|
||||||
|
$screen_name = filter_input(INPUT_GET, 'screen_name');
|
||||||
|
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT);
|
||||||
|
$stringify_ids = filter_input(INPUT_GET, 'stringify_ids', FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
|
||||||
|
'default' => self::DEFAULT_COUNT,
|
||||||
|
'min_range' => 1,
|
||||||
|
'max_range' => self::MAX_COUNT,
|
||||||
|
]]);
|
||||||
|
// Friendica-specific
|
||||||
|
$since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT);
|
||||||
|
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT, ['options' => [
|
||||||
|
'default' => 1,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
System::jsonExit(self::ids(
|
||||||
|
[Contact::SHARING, Contact::FRIEND],
|
||||||
|
self::getUid($contact_id, $screen_name),
|
||||||
|
$cursor ?? $since_id ?? - $max_id,
|
||||||
|
$count,
|
||||||
|
$stringify_ids
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2020, Friendica
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Module\Api\Twitter;
|
||||||
|
|
||||||
|
use Friendica\Core\System;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list
|
||||||
|
*/
|
||||||
|
class FriendsList extends ContactEndpoint
|
||||||
|
{
|
||||||
|
public static function rawContent(array $parameters = [])
|
||||||
|
{
|
||||||
|
// Expected value for user_id parameter: public/user contact id
|
||||||
|
$contact_id = filter_input(INPUT_GET, 'user_id' , FILTER_VALIDATE_INT);
|
||||||
|
$screen_name = filter_input(INPUT_GET, 'screen_name');
|
||||||
|
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT);
|
||||||
|
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
|
||||||
|
'default' => self::DEFAULT_COUNT,
|
||||||
|
'min_range' => 1,
|
||||||
|
'max_range' => self::MAX_COUNT,
|
||||||
|
]]);
|
||||||
|
$skip_status = filter_input(INPUT_GET, 'skip_status' , FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$include_user_entities = filter_input(INPUT_GET, 'include_user_entities', FILTER_VALIDATE_BOOLEAN);
|
||||||
|
|
||||||
|
// Friendica-specific
|
||||||
|
$since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT);
|
||||||
|
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT, ['options' => [
|
||||||
|
'default' => 1,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
System::jsonExit(self::list(
|
||||||
|
[Contact::SHARING, Contact::FRIEND],
|
||||||
|
self::getUid($contact_id, $screen_name),
|
||||||
|
$cursor ?? $since_id ?? - $max_id,
|
||||||
|
$count,
|
||||||
|
$skip_status,
|
||||||
|
$include_user_entities
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2020, Friendica
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Object\Api\Twitter;
|
||||||
|
|
||||||
|
use Friendica\BaseEntity;
|
||||||
|
use Friendica\Content\ContactSelector;
|
||||||
|
use Friendica\Content\Text\BBCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/user-object
|
||||||
|
*/
|
||||||
|
class User extends BaseEntity
|
||||||
|
{
|
||||||
|
/** @var int */
|
||||||
|
protected $id;
|
||||||
|
/** @var string */
|
||||||
|
protected $id_str;
|
||||||
|
/** @var string */
|
||||||
|
protected $name;
|
||||||
|
/** @var string */
|
||||||
|
protected $screen_name;
|
||||||
|
/** @var string|null */
|
||||||
|
protected $location;
|
||||||
|
/** @var array */
|
||||||
|
protected $derived;
|
||||||
|
/** @var string|null */
|
||||||
|
protected $url;
|
||||||
|
/** @var array */
|
||||||
|
protected $entities;
|
||||||
|
/** @var string|null */
|
||||||
|
protected $description;
|
||||||
|
/** @var bool */
|
||||||
|
protected $protected;
|
||||||
|
/** @var bool */
|
||||||
|
protected $verified;
|
||||||
|
/** @var int */
|
||||||
|
protected $followers_count;
|
||||||
|
/** @var int */
|
||||||
|
protected $friends_count;
|
||||||
|
/** @var int */
|
||||||
|
protected $listed_count;
|
||||||
|
/** @var int */
|
||||||
|
protected $favourites_count;
|
||||||
|
/** @var int */
|
||||||
|
protected $statuses_count;
|
||||||
|
/** @var string */
|
||||||
|
protected $created_at;
|
||||||
|
/** @var string */
|
||||||
|
protected $profile_banner_url;
|
||||||
|
/** @var string */
|
||||||
|
protected $profile_image_url_https;
|
||||||
|
/** @var bool */
|
||||||
|
protected $default_profile;
|
||||||
|
/** @var bool */
|
||||||
|
protected $default_profile_image;
|
||||||
|
/** @var Status */
|
||||||
|
protected $status;
|
||||||
|
/** @var array */
|
||||||
|
protected $withheld_in_countries;
|
||||||
|
/** @var string */
|
||||||
|
protected $withheld_scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $publicContact Full contact table record with uid = 0
|
||||||
|
* @param array $apcontact Optional full apcontact table record
|
||||||
|
* @param array $userContact Optional full contact table record with uid != 0
|
||||||
|
* @param bool $skip_status Whether to remove the last status property, currently unused
|
||||||
|
* @param bool $include_user_entities Whether to add the entities property
|
||||||
|
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||||
|
*/
|
||||||
|
public function __construct(array $publicContact, array $apcontact = [], array $userContact = [], $skip_status = false, $include_user_entities = true)
|
||||||
|
{
|
||||||
|
$this->id = $publicContact['id'];
|
||||||
|
$this->id_str = (string) $publicContact['id'];
|
||||||
|
$this->name = $publicContact['name'];
|
||||||
|
$this->screen_name = $publicContact['nick'] ?: $publicContact['name'];
|
||||||
|
$this->location = $publicContact['location'] ?:
|
||||||
|
ContactSelector::networkToName($publicContact['network'], $publicContact['url'], $publicContact['protocol']);
|
||||||
|
$this->derived = [];
|
||||||
|
$this->url = $publicContact['url'];
|
||||||
|
// No entities needed since we don't perform any shortening in the URL or description
|
||||||
|
$this->entities = [
|
||||||
|
'url' => ['urls' => []],
|
||||||
|
'description' => ['urls' => []],
|
||||||
|
];
|
||||||
|
if (!$include_user_entities) {
|
||||||
|
unset($this->entities);
|
||||||
|
}
|
||||||
|
$this->description = BBCode::toPlaintext($publicContact['about']);
|
||||||
|
$this->profile_image_url_https = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||||
|
$this->protected = false;
|
||||||
|
$this->followers_count = $apcontact['followers_count'] ?? 0;
|
||||||
|
$this->friends_count = $apcontact['following_count'] ?? 0;
|
||||||
|
$this->listed_count = 0;
|
||||||
|
$this->created_at = api_date($publicContact['created']);
|
||||||
|
$this->favourites_count = 0;
|
||||||
|
$this->verified = false;
|
||||||
|
$this->statuses_count = $apcontact['statuses_count'] ?? 0;
|
||||||
|
$this->profile_banner_url = '';
|
||||||
|
$this->default_profile = false;
|
||||||
|
$this->default_profile_image = false;
|
||||||
|
|
||||||
|
// @TODO Replace skip_status parameter with an optional Status parameter
|
||||||
|
unset($this->status);
|
||||||
|
|
||||||
|
// Unused optional fields
|
||||||
|
unset($this->withheld_in_countries);
|
||||||
|
unset($this->withheld_scope);
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
$this->profile_image_url = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||||
|
$this->profile_image_url_profile_size = $publicContact['thumb'];
|
||||||
|
$this->profile_image_url_large = $publicContact['photo'];
|
||||||
|
$this->utc_offset = 0;
|
||||||
|
$this->time_zone = 'UTC';
|
||||||
|
$this->geo_enabled = false;
|
||||||
|
$this->lang = null;
|
||||||
|
$this->contributors_enabled = false;
|
||||||
|
$this->is_translator = false;
|
||||||
|
$this->is_translation_enabled = false;
|
||||||
|
$this->following = false;
|
||||||
|
$this->follow_request_sent = false;
|
||||||
|
$this->statusnet_blocking = false;
|
||||||
|
$this->notifications = false;
|
||||||
|
|
||||||
|
// Friendica-specific
|
||||||
|
$this->uid = $userContact['uid'] ?? 0;
|
||||||
|
$this->cid = $userContact['id'] ?? 0;
|
||||||
|
$this->pid = $publicContact['id'];
|
||||||
|
$this->self = $userContact['self'] ?? false;
|
||||||
|
$this->network = $publicContact['network'];
|
||||||
|
$this->statusnet_profile_url = $publicContact['url'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,6 +57,10 @@ return [
|
||||||
'/profile/show' => [Module\Api\Friendica\Profile\Show::class , [R::GET ]],
|
'/profile/show' => [Module\Api\Friendica\Profile\Show::class , [R::GET ]],
|
||||||
'/events' => [Module\Api\Friendica\Events\Index::class , [R::GET ]],
|
'/events' => [Module\Api\Friendica\Events\Index::class , [R::GET ]],
|
||||||
],
|
],
|
||||||
|
'/followers/ids' => [Module\Api\Twitter\FollowersIds::class , [R::GET ]],
|
||||||
|
'/followers/list' => [Module\Api\Twitter\FollowersList::class , [R::GET ]],
|
||||||
|
'/friends/ids' => [Module\Api\Twitter\FriendsIds::class , [R::GET ]],
|
||||||
|
'/friends/list' => [Module\Api\Twitter\FriendsList::class , [R::GET ]],
|
||||||
],
|
],
|
||||||
|
|
||||||
'/admin' => [
|
'/admin' => [
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* FixtureTest class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Friendica\Test;
|
||||||
|
|
||||||
|
use Dice\Dice;
|
||||||
|
use Friendica\Core\Config\Cache;
|
||||||
|
use Friendica\Core\Config\IConfig;
|
||||||
|
use Friendica\Core\Session;
|
||||||
|
use Friendica\Core\Session\ISession;
|
||||||
|
use Friendica\Database\Database;
|
||||||
|
use Friendica\Database\DBStructure;
|
||||||
|
use Friendica\DI;
|
||||||
|
use Friendica\Test\Util\Database\StaticDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent class for test cases requiring fixtures
|
||||||
|
*/
|
||||||
|
abstract class FixtureTest extends DatabaseTest
|
||||||
|
{
|
||||||
|
/** @var Dice */
|
||||||
|
protected $dice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create variables used by tests.
|
||||||
|
*/
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->dice = (new Dice())
|
||||||
|
->addRules(include __DIR__ . '/../static/dependencies.config.php')
|
||||||
|
->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
|
||||||
|
->addRule(ISession::class, ['instanceOf' => Session\Memory::class, 'shared' => true, 'call' => null]);
|
||||||
|
DI::init($this->dice);
|
||||||
|
|
||||||
|
/** @var IConfig $config */
|
||||||
|
$configCache = $this->dice->create(Cache::class);
|
||||||
|
$configCache->set('database', 'disable_pdo', true);
|
||||||
|
|
||||||
|
/** @var Database $dba */
|
||||||
|
$dba = $this->dice->create(Database::class);
|
||||||
|
|
||||||
|
$dba->setTestmode(true);
|
||||||
|
|
||||||
|
DBStructure::checkInitialValues();
|
||||||
|
|
||||||
|
// Load the API dataset for the whole API
|
||||||
|
$this->loadFixture(__DIR__ . '/datasets/api.fixture.php', $dba);
|
||||||
|
}
|
||||||
|
}
|
|
@ -99,6 +99,45 @@ return [
|
||||||
'rel' => 2,
|
'rel' => 2,
|
||||||
'network' => 'dfrn',
|
'network' => 'dfrn',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'id' => 45,
|
||||||
|
'uid' => 0,
|
||||||
|
'name' => 'Friend contact',
|
||||||
|
'nick' => 'friendcontact',
|
||||||
|
'self' => 0,
|
||||||
|
'nurl' => 'http://localhost/profile/friendcontact',
|
||||||
|
'url' => 'http://localhost/profile/friendcontact',
|
||||||
|
'pending' => 0,
|
||||||
|
'blocked' => 0,
|
||||||
|
'rel' => 2,
|
||||||
|
'network' => 'dfrn',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 46,
|
||||||
|
'uid' => 42,
|
||||||
|
'name' => 'Mutual contact',
|
||||||
|
'nick' => 'mutualcontact',
|
||||||
|
'self' => 0,
|
||||||
|
'nurl' => 'http://localhost/profile/mutualcontact',
|
||||||
|
'url' => 'http://localhost/profile/mutualcontact',
|
||||||
|
'pending' => 0,
|
||||||
|
'blocked' => 0,
|
||||||
|
'rel' => 3,
|
||||||
|
'network' => 'dfrn',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 47,
|
||||||
|
'uid' => 0,
|
||||||
|
'name' => 'Mutual contact',
|
||||||
|
'nick' => 'mutualcontact',
|
||||||
|
'self' => 0,
|
||||||
|
'nurl' => 'http://localhost/profile/mutualcontact',
|
||||||
|
'url' => 'http://localhost/profile/mutualcontact',
|
||||||
|
'pending' => 0,
|
||||||
|
'blocked' => 0,
|
||||||
|
'rel' => 2,
|
||||||
|
'network' => 'dfrn',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'item-uri' => [
|
'item-uri' => [
|
||||||
[
|
[
|
||||||
|
|
|
@ -5,19 +5,12 @@
|
||||||
|
|
||||||
namespace Friendica\Test;
|
namespace Friendica\Test;
|
||||||
|
|
||||||
use Dice\Dice;
|
|
||||||
use Friendica\App;
|
use Friendica\App;
|
||||||
use Friendica\Core\Config\IConfig;
|
use Friendica\Core\Config\IConfig;
|
||||||
use Friendica\Core\PConfig\IPConfig;
|
use Friendica\Core\PConfig\IPConfig;
|
||||||
use Friendica\Core\Protocol;
|
use Friendica\Core\Protocol;
|
||||||
use Friendica\Core\Session;
|
|
||||||
use Friendica\Core\Session\ISession;
|
|
||||||
use Friendica\Database\Database;
|
|
||||||
use Friendica\Database\DBStructure;
|
|
||||||
use Friendica\DI;
|
use Friendica\DI;
|
||||||
use Friendica\Model\Contact;
|
|
||||||
use Friendica\Network\HTTPException;
|
use Friendica\Network\HTTPException;
|
||||||
use Friendica\Test\Util\Database\StaticDatabase;
|
|
||||||
use Friendica\Util\Temporal;
|
use Friendica\Util\Temporal;
|
||||||
use Monolog\Handler\TestHandler;
|
use Monolog\Handler\TestHandler;
|
||||||
|
|
||||||
|
@ -29,7 +22,7 @@ require_once __DIR__ . '/../../include/api.php';
|
||||||
* Functions that use header() need to be tested in a separate process.
|
* Functions that use header() need to be tested in a separate process.
|
||||||
* @see https://phpunit.de/manual/5.7/en/appendixes.annotations.html#appendixes.annotations.runTestsInSeparateProcesses
|
* @see https://phpunit.de/manual/5.7/en/appendixes.annotations.html#appendixes.annotations.runTestsInSeparateProcesses
|
||||||
*/
|
*/
|
||||||
class ApiTest extends DatabaseTest
|
class ApiTest extends FixtureTest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var TestHandler Can handle log-outputs
|
* @var TestHandler Can handle log-outputs
|
||||||
|
@ -51,9 +44,6 @@ class ApiTest extends DatabaseTest
|
||||||
/** @var IConfig */
|
/** @var IConfig */
|
||||||
protected $config;
|
protected $config;
|
||||||
|
|
||||||
/** @var Dice */
|
|
||||||
protected $dice;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create variables used by tests.
|
* Create variables used by tests.
|
||||||
*/
|
*/
|
||||||
|
@ -61,19 +51,6 @@ class ApiTest extends DatabaseTest
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$this->dice = (new Dice())
|
|
||||||
->addRules(include __DIR__ . '/../../static/dependencies.config.php')
|
|
||||||
->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
|
|
||||||
->addRule(ISession::class, ['instanceOf' => Session\Memory::class, 'shared' => true, 'call' => null]);
|
|
||||||
DI::init($this->dice);
|
|
||||||
|
|
||||||
/** @var Database $dba */
|
|
||||||
$dba = $this->dice->create(Database::class);
|
|
||||||
|
|
||||||
$dba->setTestmode(true);
|
|
||||||
|
|
||||||
DBStructure::checkInitialValues();
|
|
||||||
|
|
||||||
/** @var IConfig $config */
|
/** @var IConfig $config */
|
||||||
$this->config = $this->dice->create(IConfig::class);
|
$this->config = $this->dice->create(IConfig::class);
|
||||||
|
|
||||||
|
@ -88,8 +65,6 @@ class ApiTest extends DatabaseTest
|
||||||
$this->config->set('system', 'throttle_limit_month', 100);
|
$this->config->set('system', 'throttle_limit_month', 100);
|
||||||
$this->config->set('system', 'theme', 'system_theme');
|
$this->config->set('system', 'theme', 'system_theme');
|
||||||
|
|
||||||
// Load the API dataset for the whole API
|
|
||||||
$this->loadFixture(__DIR__ . '/../datasets/api.fixture.php', $dba);
|
|
||||||
|
|
||||||
/** @var App app */
|
/** @var App app */
|
||||||
$this->app = DI::app();
|
$this->app = DI::app();
|
||||||
|
@ -841,6 +816,22 @@ class ApiTest extends DatabaseTest
|
||||||
$this->assertEquals('ededed', $user['profile_background_color']);
|
$this->assertEquals('ededed', $user['profile_background_color']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the api_get_user() function with an empty Frio schema.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testApiGetUserWithEmptyFrioSchema()
|
||||||
|
{
|
||||||
|
$pConfig = $this->dice->create(IPConfig::class);
|
||||||
|
$pConfig->set($this->selfUser['id'], 'frio', 'schema', '---');
|
||||||
|
$user = api_get_user($this->app);
|
||||||
|
$this->assertSelfUser($user);
|
||||||
|
$this->assertEquals('708fa0', $user['profile_sidebar_fill_color']);
|
||||||
|
$this->assertEquals('6fdbe8', $user['profile_link_color']);
|
||||||
|
$this->assertEquals('ededed', $user['profile_background_color']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the api_get_user() function with a custom Frio schema.
|
* Test the api_get_user() function with a custom Frio schema.
|
||||||
*
|
*
|
||||||
|
@ -860,22 +851,6 @@ class ApiTest extends DatabaseTest
|
||||||
$this->assertEquals('123456', $user['profile_background_color']);
|
$this->assertEquals('123456', $user['profile_background_color']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the api_get_user() function with an empty Frio schema.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function testApiGetUserWithEmptyFrioSchema()
|
|
||||||
{
|
|
||||||
$pConfig = $this->dice->create(IPConfig::class);
|
|
||||||
$pConfig->set($this->selfUser['id'], 'frio', 'schema', '---');
|
|
||||||
$user = api_get_user($this->app);
|
|
||||||
$this->assertSelfUser($user);
|
|
||||||
$this->assertEquals('708fa0', $user['profile_sidebar_fill_color']);
|
|
||||||
$this->assertEquals('6fdbe8', $user['profile_link_color']);
|
|
||||||
$this->assertEquals('ededed', $user['profile_background_color']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the api_get_user() function with an user that is not allowed to use the API.
|
* Test the api_get_user() function with an user that is not allowed to use the API.
|
||||||
*
|
*
|
||||||
|
@ -2851,61 +2826,6 @@ class ApiTest extends DatabaseTest
|
||||||
$this->assertEquals('0.9.7', $result['version']);
|
$this->assertEquals('0.9.7', $result['version']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the api_ff_ids() function.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function testApiFfIds()
|
|
||||||
{
|
|
||||||
$result = api_ff_ids('json', Contact::FOLLOWER);
|
|
||||||
$this->assertEquals(['id' => []], $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the api_ff_ids() function with a result.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function testApiFfIdsWithResult()
|
|
||||||
{
|
|
||||||
$this->markTestIncomplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the api_ff_ids() function without an authenticated user.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
* @expectedException Friendica\Network\HTTPException\ForbiddenException
|
|
||||||
*/
|
|
||||||
public function testApiFfIdsWithoutAuthenticatedUser()
|
|
||||||
{
|
|
||||||
$_SESSION['authenticated'] = false;
|
|
||||||
api_ff_ids('json', Contact::FOLLOWER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the api_friends_ids() function.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function testApiFriendsIds()
|
|
||||||
{
|
|
||||||
$result = api_friends_ids('json');
|
|
||||||
$this->assertEquals(['id' => []], $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test the api_followers_ids() function.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function testApiFollowersIds()
|
|
||||||
{
|
|
||||||
$result = api_followers_ids('json');
|
|
||||||
$this->assertEquals(['id' => []], $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the api_direct_messages_new() function.
|
* Test the api_direct_messages_new() function.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Friendica\Test\src\Module\Api\Twitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ContactEndpointMock
|
||||||
|
*
|
||||||
|
* Exposes protected methods for test in the inherited class
|
||||||
|
*
|
||||||
|
* @method static int getUid(int $contact_id = null, string $screen_name = null)
|
||||||
|
* @method static array list($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $skip_status = false, bool $include_user_entities = true)
|
||||||
|
* @method static array ids($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $stringify_ids = false)
|
||||||
|
*
|
||||||
|
* @package Friendica\Test\Mock\Module\Api\Twitter
|
||||||
|
*/
|
||||||
|
class ContactEndpointMock extends \Friendica\Module\Api\Twitter\ContactEndpoint
|
||||||
|
{
|
||||||
|
public static function __callStatic($name, $arguments)
|
||||||
|
{
|
||||||
|
return self::$name(...$arguments);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,254 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Friendica\Test\src\Module\Api\Twitter;
|
||||||
|
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Module\Api\Twitter\ContactEndpoint;
|
||||||
|
use Friendica\Network\HTTPException\NotFoundException;
|
||||||
|
use Friendica\Object\Api\Twitter\User;
|
||||||
|
use Friendica\Test\FixtureTest;
|
||||||
|
|
||||||
|
class ContactEndpointTest extends FixtureTest
|
||||||
|
{
|
||||||
|
public function testGetUid()
|
||||||
|
{
|
||||||
|
$this->assertSame(42, ContactEndpointMock::getUid(42));
|
||||||
|
$this->assertSame(42, ContactEndpointMock::getUid(null, 'selfcontact'));
|
||||||
|
$this->assertSame(42, ContactEndpointMock::getUid(84, 'selfcontact'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetUidContactIdNotFound()
|
||||||
|
{
|
||||||
|
$this->expectException(NotFoundException::class);
|
||||||
|
$this->expectExceptionMessage('Contact not found');
|
||||||
|
|
||||||
|
ContactEndpointMock::getUid(84);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetUidScreenNameNotFound()
|
||||||
|
{
|
||||||
|
$this->expectException(NotFoundException::class);
|
||||||
|
$this->expectExceptionMessage('User not found');
|
||||||
|
|
||||||
|
ContactEndpointMock::getUid(null, 'othercontact');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetUidContactIdScreenNameNotFound()
|
||||||
|
{
|
||||||
|
$this->expectException(NotFoundException::class);
|
||||||
|
$this->expectExceptionMessage('User not found');
|
||||||
|
|
||||||
|
ContactEndpointMock::getUid(42, 'othercontact');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIds()
|
||||||
|
{
|
||||||
|
$expectedEmpty = [
|
||||||
|
'ids' => [],
|
||||||
|
'next_cursor' => -1,
|
||||||
|
'next_cursor_str' => '-1',
|
||||||
|
'previous_cursor' => 0,
|
||||||
|
'previous_cursor_str' => '0',
|
||||||
|
'total_count' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame($expectedEmpty, ContactEndpointMock::ids(Contact::FOLLOWER, 42));
|
||||||
|
|
||||||
|
$expectedFriend = [
|
||||||
|
'ids' => [47],
|
||||||
|
'next_cursor' => 0,
|
||||||
|
'next_cursor_str' => '0',
|
||||||
|
'previous_cursor' => 0,
|
||||||
|
'previous_cursor_str' => '0',
|
||||||
|
'total_count' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame($expectedFriend, ContactEndpointMock::ids(Contact::FRIEND, 42));
|
||||||
|
$this->assertSame($expectedFriend, ContactEndpointMock::ids([Contact::FOLLOWER, Contact::FRIEND], 42));
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::ids(Contact::SHARING, 42);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('ids', $result);
|
||||||
|
$this->assertContainsOnly('int', $result['ids']);
|
||||||
|
$this->assertSame(45, $result['ids'][0]);
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::ids([Contact::SHARING, Contact::FRIEND], 42);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('ids', $result);
|
||||||
|
$this->assertContainsOnly('int', $result['ids']);
|
||||||
|
$this->assertSame(45, $result['ids'][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testIds
|
||||||
|
*
|
||||||
|
* @throws NotFoundException
|
||||||
|
*/
|
||||||
|
public function testIdsStringify()
|
||||||
|
{
|
||||||
|
$result = ContactEndpointMock::ids(Contact::SHARING, 42, -1, ContactEndpoint::DEFAULT_COUNT, true);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('ids', $result);
|
||||||
|
$this->assertContainsOnly('string', $result['ids']);
|
||||||
|
$this->assertSame('45', $result['ids'][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIdsPagination()
|
||||||
|
{
|
||||||
|
$expectedDefaultPageResult = [
|
||||||
|
'ids' => [45],
|
||||||
|
'next_cursor' => 44,
|
||||||
|
'next_cursor_str' => '44',
|
||||||
|
'previous_cursor' => 0,
|
||||||
|
'previous_cursor_str' => '0',
|
||||||
|
'total_count' => 2,
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::ids([Contact::SHARING, Contact::FRIEND], 42, -1, 1);
|
||||||
|
|
||||||
|
$this->assertSame($expectedDefaultPageResult, $result);
|
||||||
|
|
||||||
|
$nextPageCursor = $result['next_cursor'];
|
||||||
|
|
||||||
|
$expectedSecondPageResult = [
|
||||||
|
'ids' => [47],
|
||||||
|
'next_cursor' => 46,
|
||||||
|
'next_cursor_str' => '46',
|
||||||
|
'previous_cursor' => -46,
|
||||||
|
'previous_cursor_str' => '-46',
|
||||||
|
'total_count' => 2,
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::ids([Contact::SHARING, Contact::FRIEND], 42, $nextPageCursor, 1);
|
||||||
|
|
||||||
|
$this->assertSame($expectedSecondPageResult, $result);
|
||||||
|
|
||||||
|
$firstPageCursor = $result['previous_cursor'];
|
||||||
|
$emptyNextPageCursor = $result['next_cursor'];
|
||||||
|
|
||||||
|
$expectedFirstPageResult = [
|
||||||
|
'ids' => [45],
|
||||||
|
'next_cursor' => 44,
|
||||||
|
'next_cursor_str' => '44',
|
||||||
|
'previous_cursor' => -44,
|
||||||
|
'previous_cursor_str' => '-44',
|
||||||
|
'total_count' => 2,
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::ids([Contact::SHARING, Contact::FRIEND], 42, $firstPageCursor, 1);
|
||||||
|
|
||||||
|
$this->assertSame($expectedFirstPageResult, $result);
|
||||||
|
|
||||||
|
$emptyPrevPageCursor = $result['previous_cursor'];
|
||||||
|
|
||||||
|
$expectedEmptyPrevPageResult = [
|
||||||
|
'ids' => [],
|
||||||
|
'next_cursor' => -1,
|
||||||
|
'next_cursor_str' => '-1',
|
||||||
|
'previous_cursor' => 0,
|
||||||
|
'previous_cursor_str' => '0',
|
||||||
|
'total_count' => 2,
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::ids([Contact::SHARING, Contact::FRIEND], 42, $emptyPrevPageCursor, 1);
|
||||||
|
|
||||||
|
$this->assertSame($expectedEmptyPrevPageResult, $result);
|
||||||
|
|
||||||
|
$expectedEmptyNextPageResult = [
|
||||||
|
'ids' => [],
|
||||||
|
'next_cursor' => 0,
|
||||||
|
'next_cursor_str' => '0',
|
||||||
|
'previous_cursor' => -46,
|
||||||
|
'previous_cursor_str' => '-46',
|
||||||
|
'total_count' => 2,
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::ids([Contact::SHARING, Contact::FRIEND], 42, $emptyNextPageCursor, 1);
|
||||||
|
|
||||||
|
$this->assertSame($expectedEmptyNextPageResult, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testIds
|
||||||
|
*
|
||||||
|
* @throws NotFoundException
|
||||||
|
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||||
|
* @throws \ImagickException
|
||||||
|
*/
|
||||||
|
public function testList()
|
||||||
|
{
|
||||||
|
$expectedEmpty = [
|
||||||
|
'users' => [],
|
||||||
|
'next_cursor' => -1,
|
||||||
|
'next_cursor_str' => '-1',
|
||||||
|
'previous_cursor' => 0,
|
||||||
|
'previous_cursor_str' => '0',
|
||||||
|
'total_count' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame($expectedEmpty, ContactEndpointMock::list(Contact::FOLLOWER, 42));
|
||||||
|
|
||||||
|
$expectedFriendContactUser = [
|
||||||
|
'id' => 45,
|
||||||
|
'id_str' => '45',
|
||||||
|
'name' => 'Friend contact',
|
||||||
|
'screen_name' => 'friendcontact',
|
||||||
|
'location' => 'DFRN',
|
||||||
|
'derived' => [],
|
||||||
|
'url' => 'http://localhost/profile/friendcontact',
|
||||||
|
'entities' => [
|
||||||
|
'url' => [
|
||||||
|
'urls' => [],
|
||||||
|
],
|
||||||
|
'description' => [
|
||||||
|
'urls' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'description' => '',
|
||||||
|
'protected' => false,
|
||||||
|
'verified' => false,
|
||||||
|
'followers_count' => 0,
|
||||||
|
'friends_count' => 0,
|
||||||
|
'listed_count' => 0,
|
||||||
|
'favourites_count' => 0,
|
||||||
|
'statuses_count' => 0,
|
||||||
|
'created_at' => 'Fri Feb 02 00:00:00 +0000 0000',
|
||||||
|
'profile_banner_url' => '',
|
||||||
|
'profile_image_url_https' => '',
|
||||||
|
'default_profile' => false,
|
||||||
|
'default_profile_image' => false,
|
||||||
|
'profile_image_url' => '',
|
||||||
|
'profile_image_url_profile_size' => '',
|
||||||
|
'profile_image_url_large' => '',
|
||||||
|
'utc_offset' => 0,
|
||||||
|
'time_zone' => 'UTC',
|
||||||
|
'geo_enabled' => false,
|
||||||
|
'lang' => NULL,
|
||||||
|
'contributors_enabled' => false,
|
||||||
|
'is_translator' => false,
|
||||||
|
'is_translation_enabled' => false,
|
||||||
|
'following' => false,
|
||||||
|
'follow_request_sent' => false,
|
||||||
|
'statusnet_blocking' => false,
|
||||||
|
'notifications' => false,
|
||||||
|
'uid' => 42,
|
||||||
|
'cid' => 44,
|
||||||
|
'pid' => 45,
|
||||||
|
'self' => 0,
|
||||||
|
'network' => 'dfrn',
|
||||||
|
'statusnet_profile_url' => 'http://localhost/profile/friendcontact',
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::list(Contact::SHARING, 42);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('users', $result);
|
||||||
|
$this->assertContainsOnlyInstancesOf(User::class, $result['users']);
|
||||||
|
$this->assertSame($expectedFriendContactUser, $result['users'][0]->toArray());
|
||||||
|
|
||||||
|
$result = ContactEndpointMock::list([Contact::SHARING, Contact::FRIEND], 42);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('users', $result);
|
||||||
|
$this->assertContainsOnlyInstancesOf(User::class, $result['users']);
|
||||||
|
$this->assertSame($expectedFriendContactUser, $result['users'][0]->toArray());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user