1
0
mirror of https://github.com/flarum/core.git synced 2025-08-06 08:27:42 +02:00

refactor: JSON:API (#3971)

* refactor: json:api refactor iteration 1
* chore: delete dead code
* fix: regressions
* chore: move additions/changes to package
* feat: AccessTokenResource
* feat: allow dependency injection in resources
* feat: `ApiResource` extender
* feat: improve
* feat: refactor tags extension
* feat: refactor flags extension
* fix: regressions
* fix: drop bc layer
* feat: refactor suspend extension
* feat: refactor subscriptions extension
* feat: refactor approval extension
* feat: refactor sticky extension
* feat: refactor nicknames extension
* feat: refactor mentions extension
* feat: refactor lock extension
* feat: refactor likes extension
* chore: merge conflicts
* feat: refactor extension-manager extension
* feat: context current endpoint helpers
* chore: minor
* feat: cleaner sortmap implementation
* chore: drop old package
* chore: not needed (auto scoping)
* fix: actor only fields
* refactor: simplify index endpoint
* feat: eager loading
* test: adapt
* test: phpstan
* test: adapt
* fix: typing
* fix: approving content
* tet: adapt frontend tests
* chore: typings
* chore: review
* fix: breaking change
This commit is contained in:
Sami Mazouz
2024-06-21 09:36:32 +01:00
committed by GitHub
parent 10514709f1
commit a8777c6198
296 changed files with 7148 additions and 8860 deletions

View File

@@ -1,53 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tests\integration\api;
use Flarum\Api\Controller\AbstractSerializeController;
use Flarum\Extend;
use Flarum\Testing\integration\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
use Tobscure\JsonApi\ElementInterface;
use Tobscure\JsonApi\SerializerInterface;
class AbstractSerializeControllerTest extends TestCase
{
public function test_missing_serializer_class_throws_exception()
{
$this->extend(
(new Extend\Routes('api'))
->get('/dummy-serialize', 'dummy-serialize', DummySerializeController::class)
);
$response = $this->send(
$this->request('GET', '/api/dummy-serialize')
);
$json = json_decode($contents = (string) $response->getBody(), true);
$this->assertEquals(500, $response->getStatusCode(), $contents);
$this->assertStringStartsWith('InvalidArgumentException: Serializer required for controller: '.DummySerializeController::class, $json['errors'][0]['detail']);
}
}
class DummySerializeController extends AbstractSerializeController
{
public ?string $serializer = null;
protected function data(ServerRequestInterface $request, Document $document): mixed
{
return [];
}
protected function createElement(mixed $data, SerializerInterface $serializer): ElementInterface
{
return $data;
}
}

View File

@@ -53,6 +53,7 @@ class CreateTest extends TestCase
'authenticatedAs' => $authenticatedAs,
'json' => [
'data' => [
'type' => 'access-tokens',
'attributes' => [
'title' => 'Dev'
]
@@ -61,7 +62,7 @@ class CreateTest extends TestCase
])
);
$this->assertEquals(201, $response->getStatusCode());
$this->assertEquals(201, $response->getStatusCode(), (string) $response->getBody());
}
/**
@@ -75,6 +76,7 @@ class CreateTest extends TestCase
'authenticatedAs' => $authenticatedAs,
'json' => [
'data' => [
'type' => 'access-tokens',
'attributes' => [
'title' => 'Dev'
]
@@ -83,7 +85,7 @@ class CreateTest extends TestCase
])
);
$this->assertEquals(403, $response->getStatusCode());
$this->assertEquals(403, $response->getStatusCode(), (string) $response->getBody());
}
/**
@@ -94,10 +96,16 @@ class CreateTest extends TestCase
$response = $this->send(
$this->request('POST', '/api/access-tokens', [
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'access-tokens',
'attributes' => []
]
]
])
);
$this->assertEquals(422, $response->getStatusCode());
$this->assertEquals(422, $response->getStatusCode(), (string) $response->getBody());
}
public function canCreateTokens(): array

View File

@@ -43,6 +43,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => 'Test post',
'content' => '',
@@ -52,10 +53,11 @@ class CreateTest extends TestCase
])
);
$this->assertEquals(422, $response->getStatusCode());
$body = (string) $response->getBody();
$this->assertEquals(422, $response->getStatusCode(), $body);
// The response body should contain details about the failed validation
$body = (string) $response->getBody();
$this->assertJson($body);
$this->assertEquals([
'errors' => [
@@ -79,6 +81,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => '',
'content' => 'Test post',
@@ -115,6 +118,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => 'test - too-obscure',
'content' => 'predetermined content for automated testing - too-obscure',
@@ -124,11 +128,13 @@ class CreateTest extends TestCase
])
);
$this->assertEquals(201, $response->getStatusCode());
$body = $response->getBody()->getContents();
$this->assertEquals(201, $response->getStatusCode(), $body);
/** @var Discussion $discussion */
$discussion = Discussion::firstOrFail();
$data = json_decode($response->getBody()->getContents(), true);
$data = json_decode($body, true);
$this->assertEquals('test - too-obscure', $discussion->title);
$this->assertEquals('test - too-obscure', Arr::get($data, 'data.attributes.title'));
@@ -147,6 +153,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => '我是一个土豆',
'content' => 'predetermined content for automated testing',
@@ -179,6 +186,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => '我是一个土豆',
'content' => 'predetermined content for automated testing',
@@ -206,6 +214,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => 'test - too-obscure',
'content' => 'predetermined content for automated testing - too-obscure',
@@ -220,6 +229,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => 'test - too-obscure',
'content' => 'Second predetermined content for automated testing - too-obscure',
@@ -242,6 +252,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => 'test - too-obscure',
'content' => 'predetermined content for automated testing - too-obscure',
@@ -256,6 +267,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'discussions',
'attributes' => [
'title' => 'test - too-obscure',
'content' => 'Second predetermined content for automated testing - too-obscure',

View File

@@ -56,7 +56,7 @@ class ShowTest extends TestCase
])
);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents());
}
/**
@@ -74,7 +74,7 @@ class ShowTest extends TestCase
])
);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents());
}
/**
@@ -116,7 +116,7 @@ class ShowTest extends TestCase
$json = json_decode($response->getBody()->getContents(), true);
$this->assertEquals(2, Arr::get($json, 'data.relationships.posts.data.0.id'));
$this->assertEquals(2, Arr::get($json, 'data.relationships.posts.data.0.id'), $response->getBody()->getContents());
}
/**
@@ -128,7 +128,7 @@ class ShowTest extends TestCase
$this->request('GET', '/api/discussions/2')
);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents());
}
/**

View File

@@ -45,7 +45,8 @@ class ShowTest extends TestCase
$json = json_decode($response->getBody()->getContents(), true);
$this->assertArrayNotHasKey('actor', Arr::get($json, 'data.relationships'));
$this->assertArrayHasKey('actor', Arr::get($json, 'data.relationships'));
$this->assertNull(Arr::get($json, 'data.relationships.actor.data'));
}
/**

View File

@@ -45,7 +45,7 @@ class CreateTest extends TestCase
])
);
$this->assertEquals(422, $response->getStatusCode());
$this->assertEquals(400, $response->getStatusCode(), (string) $response->getBody());
}
/**
@@ -58,6 +58,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'groups',
'attributes' => [
'nameSingular' => 'flarumite',
'namePlural' => 'flarumites',
@@ -69,10 +70,12 @@ class CreateTest extends TestCase
])
);
$this->assertEquals(201, $response->getStatusCode());
$body = $response->getBody()->getContents();
$this->assertEquals(201, $response->getStatusCode(), $body);
// Verify API response body
$data = json_decode($response->getBody()->getContents(), true);
$data = json_decode($body, true);
$this->assertEquals('flarumite', Arr::get($data, 'data.attributes.nameSingular'));
$this->assertEquals('flarumites', Arr::get($data, 'data.attributes.namePlural'));
$this->assertEquals('test', Arr::get($data, 'data.attributes.icon'));
@@ -96,6 +99,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'groups',
'attributes' => [
'nameSingular' => 'flarumite',
'namePlural' => 'flarumites',

View File

@@ -76,7 +76,7 @@ class ShowTest extends TestCase
);
// Hidden group should not be returned for guest
$this->assertEquals(404, $response->getStatusCode());
$this->assertEquals(404, $response->getStatusCode(), (string) $response->getBody());
}
/**
@@ -110,7 +110,7 @@ class ShowTest extends TestCase
// If group does not exist in database, controller
// should reject the request with 404 Not found
$this->assertEquals(404, $response->getStatusCode());
$this->assertEquals(404, $response->getStatusCode(), (string) $response->getBody());
}
protected function hiddenGroup(): array

View File

@@ -54,6 +54,6 @@ class ListTest extends TestCase
])
);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), (string) $response->getBody());
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tests\integration\api\notifications;
use Flarum\Discussion\Discussion;
use Flarum\Notification\Notification;
use Flarum\Post\Post;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Flarum\User\User;
class UpdateTest extends TestCase
{
use RetrievesAuthorizedUsers;
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->prepareDatabase([
User::class => [
$this->normalUser(),
],
Discussion::class => [
['id' => 1, 'title' => 'Foo', 'comment_count' => 1, 'user_id' => 2],
],
Post::class => [
['id' => 1, 'discussion_id' => 1, 'user_id' => 2, 'type' => 'comment', 'content' => 'Foo'],
],
Notification::class => [
['id' => 1, 'user_id' => 2, 'from_user_id' => 1, 'type' => 'discussionRenamed', 'subject_id' => 1, 'read_at' => null],
]
]);
}
/**
* @test
*/
public function can_mark_all_as_read()
{
$response = $this->send(
$this->request('PATCH', '/api/notifications/1', [
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'notifications',
'attributes' => [
'isRead' => true
],
],
],
])
);
$this->assertEquals(200, $response->getStatusCode(), (string) $response->getBody());
}
}

View File

@@ -71,18 +71,21 @@ class CreateTest extends TestCase
'authenticatedAs' => $actorId,
'json' => [
'data' => [
'type' => 'posts',
'attributes' => [
'content' => 'reply with predetermined content for automated testing - too-obscure',
],
'relationships' => [
'discussion' => ['data' => ['id' => $discussionId]],
'discussion' => [
'data' => ['type' => 'discussions', 'id' => $discussionId]
],
],
],
],
])
);
$this->assertEquals($responseStatus, $response->getStatusCode());
$this->assertEquals($responseStatus, $response->getStatusCode(), (string) $response->getBody());
}
public function discussionRepliesPrvider(): array
@@ -106,6 +109,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'posts',
'attributes' => [
'content' => 'reply with predetermined content for automated testing - too-obscure',
],
@@ -122,6 +126,7 @@ class CreateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'posts',
'attributes' => [
'content' => 'Second reply with predetermined content for automated testing - too-obscure',
],
@@ -133,6 +138,6 @@ class CreateTest extends TestCase
])
);
$this->assertEquals(429, $response->getStatusCode());
$this->assertEquals(429, $response->getStatusCode(), (string) $response->getBody());
}
}

View File

@@ -74,8 +74,10 @@ class ListTest extends TestCase
$this->request('GET', '/api/posts', ['authenticatedAs' => 1])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
$body = $response->getBody()->getContents();
$this->assertEquals(200, $response->getStatusCode(), $body);
$data = json_decode($body, true);
$this->assertEquals(5, count($data['data']));
}

View File

@@ -39,15 +39,19 @@ class CreateTest extends TestCase
'POST',
'/api/users',
[
'json' => ['data' => ['attributes' => []]],
'json' => ['data' => [
'type' => 'users',
'attributes' => [],
]],
]
)->withAttribute('bypassCsrfToken', true)
);
$this->assertEquals(422, $response->getStatusCode());
$body = (string) $response->getBody();
$this->assertEquals(422, $response->getStatusCode(), $body);
// The response body should contain details about the failed validation
$body = (string) $response->getBody();
$this->assertJson($body);
$this->assertEquals([
'errors' => [
@@ -96,7 +100,7 @@ class CreateTest extends TestCase
)->withAttribute('bypassCsrfToken', true)
);
$this->assertEquals(201, $response->getStatusCode());
$this->assertEquals(201, $response->getStatusCode(), (string) $response->getBody());
/** @var User $user */
$user = User::where('username', 'test')->firstOrFail();
@@ -227,12 +231,12 @@ class CreateTest extends TestCase
$this->assertJson($body);
$decodedBody = json_decode($body, true);
$this->assertEquals(500, $response->getStatusCode());
$this->assertEquals(422, $response->getStatusCode(), $body);
$firstError = $decodedBody['errors'][0];
// Check that the error is an invalid URI
$this->assertStringStartsWith('InvalidArgumentException: Provided avatar URL must have scheme http or https. Scheme provided was '.$regToken['scheme'].'.', $firstError['detail']);
$this->assertStringContainsString('Provided avatar URL must have scheme http or https. Scheme provided was '.$regToken['scheme'].'.', $firstError['detail']);
}
}
@@ -301,12 +305,12 @@ class CreateTest extends TestCase
$this->assertJson($body);
$decodedBody = json_decode($body, true);
$this->assertEquals(500, $response->getStatusCode());
$this->assertEquals(422, $response->getStatusCode(), $body);
$firstError = $decodedBody['errors'][0];
// Check that the error is an invalid URI
$this->assertStringStartsWith('InvalidArgumentException: Provided avatar URL must be a valid URI.', $firstError['detail']);
$this->assertStringContainsString('Provided avatar URL must be a valid URI.', $firstError['detail']);
}
}
@@ -374,7 +378,7 @@ class CreateTest extends TestCase
)->withAttribute('bypassCsrfToken', true)
);
$this->assertEquals(201, $response->getStatusCode());
$this->assertEquals(201, $response->getStatusCode(), (string) $response->getBody());
$user = User::where('username', $regToken->user_attributes['username'])->firstOrFail();

View File

@@ -86,7 +86,7 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(0, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertArrayNotHasKey('included', $responseBodyContents, json_encode($responseBodyContents));
$this->assertCount(0, $responseBodyContents['included'], json_encode($responseBodyContents));
$response = $this->createRequest(['admins'], 2);
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
@@ -99,7 +99,7 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(0, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertArrayNotHasKey('included', $responseBodyContents, json_encode($responseBodyContents));
$this->assertCount(0, $responseBodyContents['included'], json_encode($responseBodyContents));
$response = $this->createRequest(['1'], 2);
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
@@ -112,7 +112,7 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(0, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertArrayNotHasKey('included', $responseBodyContents, json_encode($responseBodyContents));
$this->assertCount(0, $responseBodyContents['included'], json_encode($responseBodyContents));
}
/**
@@ -131,7 +131,7 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(0, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertArrayNotHasKey('included', $responseBodyContents, json_encode($responseBodyContents));
$this->assertCount(0, $responseBodyContents['included'], json_encode($responseBodyContents));
}
/**
@@ -149,10 +149,8 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(4, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertCount(4, $responseBodyContents['included'], json_encode($responseBodyContents));
$this->assertEquals(1, $responseBodyContents['included'][0]['id']);
$this->assertEquals(4, $responseBodyContents['included'][1]['id']);
$this->assertEquals(5, $responseBodyContents['included'][2]['id']);
$this->assertEquals(6, $responseBodyContents['included'][3]['id']);
$this->assertEqualsCanonicalizing([1, 4, 5, 6], array_column($responseBodyContents['included'], 'id'));
}
/**
@@ -171,7 +169,7 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(0, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertArrayNotHasKey('included', $responseBodyContents, json_encode($responseBodyContents));
$this->assertCount(0, $responseBodyContents['included'], json_encode($responseBodyContents));
$response = $this->createRequest(['admins'], 1);
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
@@ -184,7 +182,7 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(0, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertArrayNotHasKey('included', $responseBodyContents, json_encode($responseBodyContents));
$this->assertCount(0, $responseBodyContents['included'], json_encode($responseBodyContents));
$response = $this->createRequest(['1'], 1);
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
@@ -197,7 +195,7 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(0, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertArrayNotHasKey('included', $responseBodyContents, json_encode($responseBodyContents));
$this->assertCount(0, $responseBodyContents['included'], json_encode($responseBodyContents));
}
/**
@@ -225,11 +223,8 @@ class GroupSearchTest extends TestCase
$responseBodyContents = json_decode($response->getBody()->getContents(), true);
$this->assertCount(5, $responseBodyContents['data'], json_encode($responseBodyContents));
$this->assertCount(5, $responseBodyContents['included'], json_encode($responseBodyContents));
$this->assertEquals(1, $responseBodyContents['included'][0]['id']);
$this->assertEquals(99, $responseBodyContents['included'][1]['id']);
$this->assertEquals(4, $responseBodyContents['included'][2]['id']);
$this->assertEquals(5, $responseBodyContents['included'][3]['id']);
$this->assertEquals(6, $responseBodyContents['included'][4]['id']);
$this->assertEqualsCanonicalizing([1, 99, 4, 5, 6], array_column($responseBodyContents['included'], 'id'));
}
private function createRequest(array $group, int $userId = null)

View File

@@ -102,6 +102,7 @@ class PasswordEmailTokensTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'email' => 'new-normal@machine.local'
]
@@ -113,7 +114,7 @@ class PasswordEmailTokensTest extends TestCase
])
);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $response->getBody());
$this->assertEquals(1, EmailToken::query()->where('user_id', 2)->count());
}

View File

@@ -45,7 +45,7 @@ class SendActivationEmailTest extends TestCase
);
// We don't want to delay tests too long.
EmailActivationThrottler::$timeout = 5;
EmailActivationThrottler::$timeout = 1;
sleep(EmailActivationThrottler::$timeout + 1);
}

View File

@@ -48,7 +48,7 @@ class SendPasswordResetEmailTest extends TestCase
);
// We don't want to delay tests too long.
PasswordResetThrottler::$timeout = 5;
PasswordResetThrottler::$timeout = 1;
sleep(PasswordResetThrottler::$timeout + 1);
}

View File

@@ -68,13 +68,15 @@ class UpdateTest extends TestCase
$response = $this->send(
$this->request('PATCH', '/api/users/2', [
'authenticatedAs' => 2,
'json' => [],
'json' => [
'data' => []
],
])
);
// Test for successful response and that the email is included in the response
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('normal@machine.local', (string) $response->getBody());
$this->assertEquals(200, $response->getStatusCode(), $body = (string) $response->getBody());
$this->assertStringContainsString('normal@machine.local', $body);
}
/**
@@ -85,13 +87,15 @@ class UpdateTest extends TestCase
$response = $this->send(
$this->request('PATCH', '/api/users/1', [
'authenticatedAs' => 2,
'json' => [],
'json' => [
'data' => []
],
])
);
// Make sure sensitive information is not made public
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringNotContainsString('admin@machine.local', (string) $response->getBody());
$this->assertEquals(200, $response->getStatusCode(), $body = (string) $response->getBody());
$this->assertStringNotContainsString('admin@machine.local', $body);
}
/**
@@ -107,7 +111,7 @@ class UpdateTest extends TestCase
])
);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), (string) $response->getBody());
}
/**
@@ -120,6 +124,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'email' => 'someOtherEmail@example.com',
]
@@ -131,7 +136,7 @@ class UpdateTest extends TestCase
])
);
$this->assertEquals(401, $response->getStatusCode());
$this->assertEquals(401, $response->getStatusCode(), (string) $response->getBody());
}
/**
@@ -144,6 +149,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'email' => 'someOtherEmail@example.com',
]
@@ -180,7 +186,7 @@ class UpdateTest extends TestCase
);
// We don't want to delay tests too long.
EmailChangeThrottler::$timeout = 5;
EmailChangeThrottler::$timeout = 1;
sleep(EmailChangeThrottler::$timeout + 1);
}
@@ -223,6 +229,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'username' => 'iCantChangeThis',
],
@@ -243,6 +250,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'preferences' => [
'something' => 'else'
@@ -268,7 +276,7 @@ class UpdateTest extends TestCase
'relationships' => [
'groups' => [
'data' => [
['id' => 1, 'type' => 'group']
['id' => 1, 'type' => 'groups']
]
]
],
@@ -289,6 +297,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'markedAllAsReadAt' => Carbon::now()
],
@@ -309,6 +318,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'isEmailConfirmed' => true
],
@@ -345,6 +355,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 3,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'email' => 'someOtherEmail@example.com',
]
@@ -368,6 +379,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 3,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'username' => 'iCantChangeThis',
],
@@ -391,7 +403,7 @@ class UpdateTest extends TestCase
'relationships' => [
'groups' => [
'data' => [
['id' => 1, 'type' => 'group']
['id' => 1, 'type' => 'groups']
]
]
],
@@ -412,6 +424,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'isEmailConfirmed' => true
],
@@ -450,6 +463,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 3,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'email' => 'someOtherEmail@example.com',
]
@@ -471,6 +485,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 3,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'username' => 'iCanChangeThis',
],
@@ -492,6 +507,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 3,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'email' => 'someOtherEmail@example.com',
]
@@ -513,6 +529,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 3,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'username' => 'iCanChangeThis',
],
@@ -537,7 +554,7 @@ class UpdateTest extends TestCase
'relationships' => [
'groups' => [
'data' => [
['id' => 4, 'type' => 'group']
['id' => 4, 'type' => 'groups']
]
]
],
@@ -545,7 +562,7 @@ class UpdateTest extends TestCase
],
])
);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), (string) $response->getBody());
}
/**
@@ -585,7 +602,7 @@ class UpdateTest extends TestCase
'relationships' => [
'groups' => [
'data' => [
['id' => 1, 'type' => 'group']
['id' => 1, 'type' => 'groups']
]
]
],
@@ -610,7 +627,7 @@ class UpdateTest extends TestCase
'relationships' => [
'groups' => [
'data' => [
['id' => 1, 'type' => 'group']
['id' => 1, 'type' => 'groups']
]
]
],
@@ -632,6 +649,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 2,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'isEmailConfirmed' => true
],
@@ -652,6 +670,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'preferences' => [
'something' => 'else'
@@ -674,6 +693,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'markedAllAsReadAt' => Carbon::now()
],
@@ -694,6 +714,7 @@ class UpdateTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => 'users',
'attributes' => [
'isEmailConfirmed' => true
],
@@ -724,7 +745,7 @@ class UpdateTest extends TestCase
],
])
);
$this->assertEquals(403, $response->getStatusCode());
$this->assertEquals(403, $response->getStatusCode(), (string) $response->getBody());
}
/**

View File

@@ -10,24 +10,23 @@
namespace Flarum\Tests\integration\extenders;
use Carbon\Carbon;
use Flarum\Api\Controller\AbstractShowController;
use Flarum\Api\Controller\ListDiscussionsController;
use Flarum\Api\Controller\ListUsersController;
use Flarum\Api\Controller\ShowDiscussionController;
use Flarum\Api\Controller\ShowForumController;
use Flarum\Api\Controller\ShowPostController;
use Flarum\Api\Controller\ShowUserController;
use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Api\Serializer\ForumSerializer;
use Flarum\Api\Serializer\PostSerializer;
use Flarum\Api\Serializer\UserSerializer;
use Flarum\Api\Context;
use Flarum\Api\Endpoint\Index;
use Flarum\Api\Endpoint\Show;
use Flarum\Api\Resource\AbstractDatabaseResource;
use Flarum\Api\Resource\DiscussionResource;
use Flarum\Api\Resource\UserResource;
use Flarum\Api\Schema\Relationship\ToMany;
use Flarum\Api\Sort\SortColumn;
use Flarum\Discussion\Discussion;
use Flarum\Extend;
use Flarum\Foundation\ValidationException;
use Flarum\Post\Post;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Flarum\User\User;
use Illuminate\Support\Arr;
use Tobyz\JsonApiServer\Schema\Field\Field;
class ApiControllerTest extends TestCase
{
@@ -52,6 +51,7 @@ class ApiControllerTest extends TestCase
Post::class => [
['id' => 1, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'discussionRenamed', 'content' => '<t><p>can i haz relationz?</p></t>'],
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'discussionRenamed', 'content' => '<t><p>can i haz relationz?</p></t>'],
['id' => 3, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'discussionRenamed', 'content' => '<t><p>can i haz relationz?</p></t>'],
['id' => 3, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'discussionRenamed', 'content' => '<t><p>can i haz relationz?</p></t>'],
],
]);
@@ -60,12 +60,16 @@ class ApiControllerTest extends TestCase
/**
* @test
*/
public function prepare_data_serialization_callback_works_if_added()
public function after_endpoint_callback_works_if_added()
{
$this->extend(
(new Extend\ApiController(ShowDiscussionController::class))
->prepareDataForSerialization(function ($controller, Discussion $discussion) {
$discussion->title = 'dataSerializationPrepCustomTitle';
(new Extend\ApiResource(DiscussionResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->after(function ($context, Discussion $discussion) {
$discussion->title = 'dataSerializationPrepCustomTitle';
return $discussion;
});
})
);
@@ -75,19 +79,21 @@ class ApiControllerTest extends TestCase
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$payload = json_decode($body = $response->getBody()->getContents(), true);
$this->assertEquals('dataSerializationPrepCustomTitle', $payload['data']['attributes']['title']);
$this->assertEquals('dataSerializationPrepCustomTitle', $payload['data']['attributes']['title'], $body);
}
/**
* @test
*/
public function prepare_data_serialization_callback_works_with_invokable_classes()
public function after_endpoint_callback_works_with_invokable_classes()
{
$this->extend(
(new Extend\ApiController(ShowDiscussionController::class))
->prepareDataForSerialization(CustomPrepareDataSerializationInvokableClass::class)
(new Extend\ApiResource(DiscussionResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->after(CustomAfterEndpointInvokableClass::class);
})
);
$response = $this->send(
@@ -96,72 +102,26 @@ class ApiControllerTest extends TestCase
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$payload = json_decode($body = $response->getBody()->getContents(), true);
$this->assertEquals(CustomPrepareDataSerializationInvokableClass::class, $payload['data']['attributes']['title']);
$this->assertEquals(CustomAfterEndpointInvokableClass::class, $payload['data']['attributes']['title'], $body);
}
/**
* @test
*/
public function prepare_data_serialization_allows_passing_args_by_reference_with_closures()
public function after_endpoint_callback_works_if_added_to_parent_class()
{
$this->extend(
(new Extend\ApiSerializer(ForumSerializer::class))
->hasMany('referenceTest', UserSerializer::class),
(new Extend\ApiController(ShowForumController::class))
->addInclude('referenceTest')
->prepareDataForSerialization(function ($controller, &$data) {
$data['referenceTest'] = User::limit(2)->get();
})
);
(new Extend\ApiResource(AbstractDatabaseResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->after(function (Context $context, object $model) {
if ($context->collection instanceof DiscussionResource) {
$model->title = 'dataSerializationPrepCustomTitle2';
}
$response = $this->send(
$this->request('GET', '/api', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('referenceTest', $payload['data']['relationships']);
}
/**
* @test
*/
public function prepare_data_serialization_allows_passing_args_by_reference_with_invokable_classes()
{
$this->extend(
(new Extend\ApiSerializer(ForumSerializer::class))
->hasMany('referenceTest2', UserSerializer::class),
(new Extend\ApiController(ShowForumController::class))
->addInclude('referenceTest2')
->prepareDataForSerialization(CustomInvokableClassArgsReference::class)
);
$response = $this->send(
$this->request('GET', '/api', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('referenceTest2', $payload['data']['relationships']);
}
/**
* @test
*/
public function prepare_data_serialization_callback_works_if_added_to_parent_class()
{
$this->extend(
(new Extend\ApiController(AbstractShowController::class))
->prepareDataForSerialization(function ($controller, Discussion $discussion) {
if ($controller instanceof ShowDiscussionController) {
$discussion->title = 'dataSerializationPrepCustomTitle2';
}
return $model;
});
})
);
@@ -179,18 +139,26 @@ class ApiControllerTest extends TestCase
/**
* @test
*/
public function prepare_data_serialization_callback_prioritizes_child_classes()
public function after_endpoint_callback_prioritizes_child_classes()
{
$this->extend(
(new Extend\ApiController(AbstractShowController::class))
->prepareDataForSerialization(function ($controller, Discussion $discussion) {
if ($controller instanceof ShowDiscussionController) {
$discussion->title = 'dataSerializationPrepCustomTitle3';
}
(new Extend\ApiResource(DiscussionResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->after(function (Context $context, object $model) {
$model->title = 'dataSerializationPrepCustomTitle4';
return $model;
});
}),
(new Extend\ApiController(ShowDiscussionController::class))
->prepareDataForSerialization(function ($controller, Discussion $discussion) {
$discussion->title = 'dataSerializationPrepCustomTitle4';
(new Extend\ApiResource(AbstractDatabaseResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->after(function (Context $context, object $model) {
if ($context->collection instanceof DiscussionResource) {
$model->title = 'dataSerializationPrepCustomTitle3';
}
return $model;
});
})
);
@@ -200,22 +168,22 @@ class ApiControllerTest extends TestCase
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$payload = json_decode($body = $response->getBody()->getContents(), true);
$this->assertEquals('dataSerializationPrepCustomTitle4', $payload['data']['attributes']['title']);
$this->assertEquals('dataSerializationPrepCustomTitle4', $payload['data']['attributes']['title'], $body);
}
/**
* @test
*/
public function prepare_data_query_callback_works_if_added_to_parent_class()
public function before_endpoint_callback_works_if_added_to_parent_class()
{
$this->extend(
(new Extend\ApiController(AbstractShowController::class))
->prepareDataQuery(function ($controller) {
if ($controller instanceof ShowDiscussionController) {
$controller->setSerializer(CustomDiscussionSerializer2::class);
}
(new Extend\ApiResource(AbstractDatabaseResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->before(function () {
throw new ValidationException(['field' => 'error on purpose']);
});
})
);
@@ -225,26 +193,29 @@ class ApiControllerTest extends TestCase
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$body = $response->getBody()->getContents();
$this->assertArrayHasKey('customSerializer2', $payload['data']['attributes']);
$this->assertEquals(422, $response->getStatusCode(), $body);
$this->assertStringContainsString('error on purpose', $body, $body);
}
/**
* @test
*/
public function prepare_data_query_callback_prioritizes_child_classes()
public function before_endpoint_callback_prioritizes_child_classes()
{
$this->extend(
(new Extend\ApiController(AbstractShowController::class))
->prepareDataForSerialization(function ($controller) {
if ($controller instanceof ShowDiscussionController) {
$controller->setSerializer(CustomDiscussionSerializer2::class);
}
(new Extend\ApiResource(DiscussionResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->before(function () {
throw new ValidationException(['field' => 'error on purpose from exact resource']);
});
}),
(new Extend\ApiController(ShowDiscussionController::class))
->prepareDataForSerialization(function ($controller) {
$controller->setSerializer(CustomDiscussionSerializer::class);
(new Extend\ApiResource(AbstractDatabaseResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->before(function () {
throw new ValidationException(['field' => 'error on purpose from abstract resource']);
});
})
);
@@ -254,95 +225,10 @@ class ApiControllerTest extends TestCase
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$body = $response->getBody()->getContents();
$this->assertArrayHasKey('customSerializer', $payload['data']['attributes']);
}
/**
* @test
*/
public function custom_serializer_doesnt_work_by_default()
{
$response = $this->send(
$this->request('GET', '/api/discussions/1', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayNotHasKey('customSerializer', $payload['data']['attributes']);
}
/**
* @test
*/
public function custom_serializer_works_if_set()
{
$this->extend(
(new Extend\ApiController(ShowDiscussionController::class))
->setSerializer(CustomDiscussionSerializer::class)
);
$response = $this->send(
$this->request('GET', '/api/discussions/1', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('customSerializer', $payload['data']['attributes']);
}
/**
* @test
*/
public function custom_serializer_works_if_set_with_invokable_class()
{
$this->extend(
(new Extend\ApiController(ShowPostController::class))
->setSerializer(CustomPostSerializer::class, CustomApiControllerInvokableClass::class)
);
$this->prepareDatabase([
Post::class => [
['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>foo bar</p></t>'],
],
]);
$response = $this->send(
$this->request('GET', '/api/posts/1', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('customSerializer', $payload['data']['attributes']);
}
/**
* @test
*/
public function custom_serializer_doesnt_work_with_false_callback_return()
{
$this->extend(
(new Extend\ApiController(ShowUserController::class))
->setSerializer(CustomUserSerializer::class, function () {
return false;
})
);
$response = $this->send(
$this->request('GET', '/api/users/2', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayNotHasKey('customSerializer', $payload['data']['attributes']);
$this->assertEquals(422, $response->getStatusCode(), $body);
$this->assertStringContainsString('error on purpose from abstract resource', $body, $body);
}
/**
@@ -370,10 +256,15 @@ class ApiControllerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasMany('customApiControllerRelation', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(UserSerializer::class))
->hasMany('customApiControllerRelation', DiscussionSerializer::class),
(new Extend\ApiController(ShowUserController::class))
->addInclude('customApiControllerRelation')
(new Extend\ApiResource(UserResource::class))
->fields(fn () => [
ToMany::make('customApiControllerRelation')
->type('discussions')
->includable(),
])
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->addDefaultInclude(['customApiControllerRelation']);
})
);
$response = $this->send(
@@ -382,9 +273,9 @@ class ApiControllerTest extends TestCase
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$payload = json_decode($body = $response->getBody()->getContents(), true);
$this->assertArrayHasKey('customApiControllerRelation', $payload['data']['relationships']);
$this->assertArrayHasKey('customApiControllerRelation', $payload['data']['relationships'] ?? [], $body);
}
/**
@@ -395,10 +286,12 @@ class ApiControllerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasMany('customApiControllerRelation2', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(UserSerializer::class))
->hasMany('customApiControllerRelation2', DiscussionSerializer::class),
(new Extend\ApiController(ShowUserController::class))
->addOptionalInclude('customApiControllerRelation2')
(new Extend\ApiResource(UserResource::class))
->fields(fn () => [
ToMany::make('customApiControllerRelation2')
->type('discussions')
->includable(),
])
);
$response = $this->send(
@@ -411,7 +304,7 @@ class ApiControllerTest extends TestCase
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('customApiControllerRelation2', $payload['data']['relationships']);
$this->assertArrayHasKey('customApiControllerRelation2', $payload['data']['relationships'] ?? []);
}
/**
@@ -436,8 +329,10 @@ class ApiControllerTest extends TestCase
public function custom_relationship_not_included_if_removed()
{
$this->extend(
(new Extend\ApiController(ShowUserController::class))
->removeInclude('groups')
(new Extend\ApiResource(UserResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->removeDefaultInclude(['groups']);
})
);
$response = $this->send(
@@ -459,11 +354,13 @@ class ApiControllerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasMany('customApiControllerRelation2', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(UserSerializer::class))
->hasMany('customApiControllerRelation2', DiscussionSerializer::class),
(new Extend\ApiController(ShowUserController::class))
->addOptionalInclude('customApiControllerRelation2')
->removeOptionalInclude('customApiControllerRelation2')
(new Extend\ApiResource(UserResource::class))
->fields(fn () => [
ToMany::make('customApiControllerRelation2')
->type('discussions')
->includable(),
])
->field('customApiControllerRelation2', fn (Field $field) => $field->includable(false))
);
$response = $this->send(
@@ -499,8 +396,10 @@ class ApiControllerTest extends TestCase
public function custom_limit_works_if_set()
{
$this->extend(
(new Extend\ApiController(ListDiscussionsController::class))
->setLimit(1)
(new Extend\ApiResource(DiscussionResource::class))
->endpoint(Index::class, function (Index $endpoint): Index {
return $endpoint->limit(1);
})
);
$response = $this->send(
@@ -520,8 +419,10 @@ class ApiControllerTest extends TestCase
public function custom_max_limit_works_if_set()
{
$this->extend(
(new Extend\ApiController(ListDiscussionsController::class))
->setMaxLimit(1)
(new Extend\ApiResource(DiscussionResource::class))
->endpoint(Index::class, function (Index $endpoint): Index {
return $endpoint->maxLimit(1);
})
);
$response = $this->send(
@@ -553,37 +454,16 @@ class ApiControllerTest extends TestCase
$this->assertEquals(400, $response->getStatusCode());
}
/**
* @test
*/
public function custom_sort_field_doesnt_work_with_false_callback_return()
{
$this->extend(
(new Extend\ApiController(ListDiscussionsController::class))
->addSortField('userId', function () {
return false;
})
);
$response = $this->send(
$this->request('GET', '/api/discussions', [
'authenticatedAs' => 1,
])->withQueryParams([
'sort' => 'userId',
])
);
$this->assertEquals(400, $response->getStatusCode());
}
/**
* @test
*/
public function custom_sort_field_exists_if_added()
{
$this->extend(
(new Extend\ApiController(ListDiscussionsController::class))
->addSortField('userId')
(new Extend\ApiResource(DiscussionResource::class))
->sorts(fn () => [
SortColumn::make('userId')
]),
);
$response = $this->send(
@@ -594,9 +474,9 @@ class ApiControllerTest extends TestCase
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$payload = json_decode($body = $response->getBody()->getContents(), true);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $body);
$this->assertEquals([3, 1, 2], Arr::pluck($payload['data'], 'id'));
}
@@ -622,8 +502,8 @@ class ApiControllerTest extends TestCase
public function custom_sort_field_doesnt_exist_if_removed()
{
$this->extend(
(new Extend\ApiController(ListDiscussionsController::class))
->removeSortField('createdAt')
(new Extend\ApiResource(DiscussionResource::class))
->removeSorts(['createdAt'])
);
$response = $this->send(
@@ -634,7 +514,7 @@ class ApiControllerTest extends TestCase
])
);
$this->assertEquals(400, $response->getStatusCode());
$this->assertEquals(400, $response->getStatusCode(), $response->getBody()->getContents());
}
/**
@@ -643,9 +523,13 @@ class ApiControllerTest extends TestCase
public function custom_sort_field_works_if_set()
{
$this->extend(
(new Extend\ApiController(ListDiscussionsController::class))
->addSortField('userId')
->setSort(['userId' => 'desc'])
(new Extend\ApiResource(DiscussionResource::class))
->sorts(fn () => [
SortColumn::make('userId')
])
->endpoint(Index::class, function (Index $endpoint): Index {
return $endpoint->defaultSort('-userId');
})
);
$response = $this->send(
@@ -670,11 +554,13 @@ class ApiControllerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\ApiController(ListUsersController::class))
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
(new Extend\ApiResource(UserResource::class))
->endpoint(Index::class, function (Index $endpoint) use (&$users) {
return $endpoint->after(function ($context, $data) use (&$users) {
$users = $data;
return [];
return $data;
});
})
);
@@ -697,22 +583,25 @@ class ApiControllerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\ApiController(ListUsersController::class))
->load('firstLevelRelation')
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
(new Extend\ApiResource(UserResource::class))
->endpoint(Index::class, function (Index $endpoint) use (&$users) {
return $endpoint
->eagerLoad('firstLevelRelation')
->after(function ($context, $data) use (&$users) {
$users = $data;
return [];
return $data;
});
})
);
$this->send(
$response = $this->send(
$this->request('GET', '/api/users', [
'authenticatedAs' => 1,
])
);
$this->assertFalse($users->filter->relationLoaded('firstLevelRelation')->isEmpty());
$this->assertFalse($users->filter->relationLoaded('firstLevelRelation')->isEmpty(), $response->getBody()->getContents());
}
/**
@@ -725,13 +614,14 @@ class ApiControllerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\Model(Post::class))
->belongsTo('secondLevelRelation', Discussion::class),
(new Extend\ApiController(ListUsersController::class))
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
(new Extend\ApiResource(UserResource::class))
->endpoint(Index::class, function (Index $endpoint) use (&$users) {
return $endpoint
->after(function ($context, $data) use (&$users) {
$users = $data;
return [];
return $data;
});
})
);
@@ -756,12 +646,15 @@ class ApiControllerTest extends TestCase
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\Model(Post::class))
->belongsTo('secondLevelRelation', Discussion::class),
(new Extend\ApiController(ListUsersController::class))
->load(['firstLevelRelation', 'firstLevelRelation.secondLevelRelation'])
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
(new Extend\ApiResource(UserResource::class))
->endpoint(Index::class, function (Index $endpoint) use (&$users) {
return $endpoint
->eagerLoad(['firstLevelRelation.secondLevelRelation'])
->after(function ($context, $data) use (&$users) {
$users = $data;
return [];
return $data;
});
})
);
@@ -784,14 +677,15 @@ class ApiControllerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\Model(Post::class))
->belongsTo('secondLevelRelation', Discussion::class),
(new Extend\ApiController(ListUsersController::class))
->load(['firstLevelRelation.secondLevelRelation'])
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
(new Extend\ApiResource(UserResource::class))
->endpoint(Index::class, function (Index $endpoint) use (&$users) {
return $endpoint
->eagerLoadWhenIncluded(['firstLevelRelation' => ['secondLevelRelation']])
->after(function ($context, $data) use (&$users) {
$users = $data;
return [];
return $data;
});
})
);
@@ -814,12 +708,15 @@ class ApiControllerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\ApiController(ListUsersController::class))
->loadWhere('firstLevelRelation', function ($query, $request) {})
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
(new Extend\ApiResource(UserResource::class))
->endpoint(Index::class, function (Index $endpoint) use (&$users) {
return $endpoint
->eagerLoadWhere('firstLevelRelation', function ($query, $request) {})
->after(function ($context, $data) use (&$users) {
$users = $data;
return [];
return $data;
});
})
);
@@ -844,13 +741,16 @@ class ApiControllerTest extends TestCase
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\Model(Post::class))
->belongsTo('secondLevelRelation', Discussion::class),
(new Extend\ApiController(ListUsersController::class))
->load('firstLevelRelation')
->loadWhere('firstLevelRelation.secondLevelRelation', function ($query, $request) {})
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
(new Extend\ApiResource(UserResource::class))
->endpoint(Index::class, function (Index $endpoint) use (&$users) {
return $endpoint
->eagerLoad('firstLevelRelation')
->eagerLoadWhere('firstLevelRelation.secondLevelRelation', function ($query, $request) {})
->after(function ($context, $data) use (&$users) {
$users = $data;
return [];
return $data;
});
})
);
@@ -862,129 +762,14 @@ class ApiControllerTest extends TestCase
$this->assertFalse($users->pluck('firstLevelRelation')->filter->relationLoaded('secondLevelRelation')->isEmpty());
}
/**
* @test
*/
public function custom_callable_second_level_relation_is_not_loaded_when_first_level_is_not()
{
$users = null;
$this->extend(
(new Extend\Model(User::class))
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\Model(Post::class))
->belongsTo('secondLevelRelation', Discussion::class),
(new Extend\ApiController(ListUsersController::class))
->loadWhere('firstLevelRelation.secondLevelRelation', function ($query, $request) {})
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
return [];
})
);
$this->send(
$this->request('GET', '/api/users', [
'authenticatedAs' => 1,
])
);
$this->assertTrue($users->pluck('firstLevelRelation')->filter->relationLoaded('secondLevelRelation')->isEmpty());
}
/**
* @test
*/
public function custom_callable_second_level_relation_is_loaded_when_first_level_is()
{
$users = null;
$this->extend(
(new Extend\Model(User::class))
->hasOne('firstLevelRelation', Post::class, 'user_id'),
(new Extend\Model(Post::class))
->belongsTo('secondLevelRelation', Discussion::class),
(new Extend\ApiController(ListUsersController::class))
->loadWhere('firstLevelRelation', function ($query, $request) {})
->loadWhere('firstLevelRelation.secondLevelRelation', function ($query, $request) {})
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
$users = $data;
return [];
})
);
$this->send(
$this->request('GET', '/api/users', [
'authenticatedAs' => 1,
])
);
$this->assertTrue($users->pluck('firstLevelRelation')->filter->relationLoaded('secondLevelRelation')->isNotEmpty());
}
}
class CustomDiscussionSerializer extends DiscussionSerializer
class CustomAfterEndpointInvokableClass
{
protected function getDefaultAttributes(object|array $model): array
{
return parent::getDefaultAttributes($model) + [
'customSerializer' => true
];
}
}
class CustomDiscussionSerializer2 extends DiscussionSerializer
{
protected function getDefaultAttributes(object|array $model): array
{
return parent::getDefaultAttributes($model) + [
'customSerializer2' => true
];
}
}
class CustomUserSerializer extends UserSerializer
{
protected function getDefaultAttributes(object|array $model): array
{
return parent::getDefaultAttributes($model) + [
'customSerializer' => true
];
}
}
class CustomPostSerializer extends PostSerializer
{
protected function getDefaultAttributes(object|array $model): array
{
return parent::getDefaultAttributes($model) + [
'customSerializer' => true
];
}
}
class CustomApiControllerInvokableClass
{
public function __invoke()
{
return true;
}
}
class CustomPrepareDataSerializationInvokableClass
{
public function __invoke(ShowDiscussionController $controller, Discussion $discussion)
public function __invoke(Context $context, Discussion $discussion): Discussion
{
$discussion->title = __CLASS__;
}
}
class CustomInvokableClassArgsReference
{
public function __invoke($controller, &$data)
{
$data['referenceTest2'] = User::limit(2)->get();
return $discussion;
}
}

View File

@@ -10,13 +10,11 @@
namespace Flarum\Tests\integration\extenders;
use Carbon\Carbon;
use Flarum\Api\Controller\ShowUserController;
use Flarum\Api\Serializer\AbstractSerializer;
use Flarum\Api\Serializer\BasicUserSerializer;
use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Api\Serializer\ForumSerializer;
use Flarum\Api\Serializer\PostSerializer;
use Flarum\Api\Serializer\UserSerializer;
use Flarum\Api\Endpoint\Show;
use Flarum\Api\Resource\AbstractDatabaseResource;
use Flarum\Api\Resource\ForumResource;
use Flarum\Api\Resource\UserResource;
use Flarum\Api\Schema;
use Flarum\Discussion\Discussion;
use Flarum\Extend;
use Flarum\Post\Post;
@@ -74,12 +72,11 @@ class ApiSerializerTest extends TestCase
public function custom_attributes_exist_if_added()
{
$this->extend(
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return [
'customAttribute' => true
];
})
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Schema\Boolean::make('customAttribute')
->get(fn () => true),
])
);
$this->app();
@@ -101,8 +98,8 @@ class ApiSerializerTest extends TestCase
public function custom_attributes_with_invokable_exist_if_added()
{
$this->extend(
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(CustomAttributesInvokableClass::class)
(new Extend\ApiResource(ForumResource::class))
->fields(CustomAttributesInvokableClass::class)
);
$this->app();
@@ -124,12 +121,11 @@ class ApiSerializerTest extends TestCase
public function custom_attributes_exist_if_added_to_parent_class()
{
$this->extend(
(new Extend\ApiSerializer(BasicUserSerializer::class))
->attributes(function () {
return [
'customAttribute' => true
];
})
(new Extend\ApiResource(AbstractDatabaseResource::class))
->fields(fn () => [
Schema\Boolean::make('customAttribute')
->get(fn () => true),
])
);
$this->app();
@@ -151,18 +147,16 @@ class ApiSerializerTest extends TestCase
public function custom_attributes_prioritize_child_classes()
{
$this->extend(
(new Extend\ApiSerializer(BasicUserSerializer::class))
->attributes(function () {
return [
'customAttribute' => 'initialValue'
];
}),
(new Extend\ApiSerializer(UserSerializer::class))
->attributes(function () {
return [
'customAttribute' => 'newValue'
];
})
(new Extend\ApiResource(AbstractDatabaseResource::class))
->fields(fn () => [
Schema\Str::make('customAttribute')
->get(fn () => 'initialValue')
]),
(new Extend\ApiResource(UserResource::class))
->fields(fn () => [
Schema\Str::make('customAttribute')
->get(fn () => 'newValue')
]),
);
$this->app();
@@ -179,130 +173,27 @@ class ApiSerializerTest extends TestCase
$this->assertEquals('newValue', $payload['data']['attributes']['customAttribute']);
}
/**
* @test
*/
public function custom_single_attribute_exists_if_added()
{
$this->extend(
(new Extend\ApiSerializer(ForumSerializer::class))
->attribute('customSingleAttribute', function () {
return true;
})->attribute('customSingleAttribute_0', function () {
return 0;
})
);
$this->app();
$response = $this->send(
$this->request('GET', '/api', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('customSingleAttribute', $payload['data']['attributes']);
$this->assertArrayHasKey('customSingleAttribute_0', $payload['data']['attributes']);
$this->assertEquals(0, $payload['data']['attributes']['customSingleAttribute_0']);
}
/**
* @test
*/
public function custom_single_attribute_with_invokable_exists_if_added()
{
$this->extend(
(new Extend\ApiSerializer(ForumSerializer::class))
->attribute('customSingleAttribute_1', CustomSingleAttributeInvokableClass::class)
);
$this->app();
$response = $this->send(
$this->request('GET', '/api', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('customSingleAttribute_1', $payload['data']['attributes']);
}
/**
* @test
*/
public function custom_single_attribute_exists_if_added_to_parent_class()
{
$this->extend(
(new Extend\ApiSerializer(BasicUserSerializer::class))
->attribute('customSingleAttribute_2', function () {
return true;
})
);
$this->app();
$response = $this->send(
$this->request('GET', '/api/users/2', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('customSingleAttribute_2', $payload['data']['attributes']);
}
/**
* @test
*/
public function custom_single_attribute_prioritizes_child_classes()
{
$this->extend(
(new Extend\ApiSerializer(BasicUserSerializer::class))
->attribute('customSingleAttribute_3', function () {
return 'initialValue';
}),
(new Extend\ApiSerializer(UserSerializer::class))
->attribute('customSingleAttribute_3', function () {
return 'newValue';
})
);
$this->app();
$response = $this->send(
$this->request('GET', '/api/users/2', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('customSingleAttribute_3', $payload['data']['attributes']);
$this->assertEquals('newValue', $payload['data']['attributes']['customSingleAttribute_3']);
}
/**
* @test
*/
public function custom_attributes_can_be_overriden()
{
$this->extend(
(new Extend\ApiSerializer(BasicUserSerializer::class))
->attribute('someCustomAttribute', function () {
return 'newValue';
})->attributes(function () {
return [
'someCustomAttribute' => 'initialValue',
'someOtherCustomAttribute' => 'initialValue',
];
})->attribute('someOtherCustomAttribute', function () {
return 'newValue';
})
(new Extend\ApiResource(UserResource::class))
->fields(fn () => [
Schema\Str::make('someCustomAttribute')
->get(fn () => 'newValue'),
])
->fields(fn () => [
Schema\Str::make('someCustomAttribute')
->get(fn () => 'secondValue'),
Schema\Str::make('someOtherCustomAttribute')
->get(fn () => 'secondValue'),
])
->fields(fn () => [
Schema\Str::make('someOtherCustomAttribute')
->get(fn () => 'newValue'),
])
);
$this->app();
@@ -316,7 +207,7 @@ class ApiSerializerTest extends TestCase
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('someCustomAttribute', $payload['data']['attributes']);
$this->assertEquals('newValue', $payload['data']['attributes']['someCustomAttribute']);
$this->assertEquals('secondValue', $payload['data']['attributes']['someCustomAttribute']);
$this->assertArrayHasKey('someOtherCustomAttribute', $payload['data']['attributes']);
$this->assertEquals('newValue', $payload['data']['attributes']['someOtherCustomAttribute']);
}
@@ -327,8 +218,10 @@ class ApiSerializerTest extends TestCase
public function custom_relations_dont_exist_by_default()
{
$this->extend(
(new Extend\ApiController(ShowUserController::class))
->addInclude(['customSerializerRelation', 'postCustomRelation', 'anotherCustomRelation'])
(new Extend\ApiResource(UserResource::class))
->endpoint(Show::class, function (Show $endpoint): Show {
return $endpoint->addDefaultInclude(['customSerializerRelation', 'postCustomRelation', 'anotherCustomRelation']);
})
);
$response = $this->send(
@@ -337,11 +230,7 @@ class ApiSerializerTest extends TestCase
])
);
$responseJson = json_decode($response->getBody(), true);
$this->assertArrayNotHasKey('customSerializerRelation', $responseJson['data']['relationships']);
$this->assertArrayNotHasKey('postCustomRelation', $responseJson['data']['relationships']);
$this->assertArrayNotHasKey('anotherCustomRelation', $responseJson['data']['relationships']);
$this->assertEquals(400, $response->getStatusCode());
}
/**
@@ -352,10 +241,15 @@ class ApiSerializerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasMany('customSerializerRelation', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(UserSerializer::class))
->hasMany('customSerializerRelation', DiscussionSerializer::class),
(new Extend\ApiController(ShowUserController::class))
->addInclude('customSerializerRelation')
(new Extend\ApiResource(UserResource::class))
->fields(fn () => [
Schema\Relationship\ToMany::make('customSerializerRelation')
->type('discussions')
->includable()
])
->endpoint(Show::class, function (Show $endpoint) {
return $endpoint->addDefaultInclude(['customSerializerRelation']);
})
);
$response = $this->send(
@@ -378,64 +272,15 @@ class ApiSerializerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasOne('customSerializerRelation', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(UserSerializer::class))
->hasOne('customSerializerRelation', DiscussionSerializer::class),
(new Extend\ApiController(ShowUserController::class))
->addInclude('customSerializerRelation')
);
$response = $this->send(
$this->request('GET', '/api/users/2', [
'authenticatedAs' => 1,
])
);
$responseJson = json_decode($response->getBody(), true);
$this->assertArrayHasKey('customSerializerRelation', $responseJson['data']['relationships']);
$this->assertEquals('discussions', $responseJson['data']['relationships']['customSerializerRelation']['data']['type']);
}
/**
* @test
*/
public function custom_relationship_exists_if_added()
{
$this->extend(
(new Extend\Model(User::class))
->hasOne('customSerializerRelation', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(UserSerializer::class))
->relationship('customSerializerRelation', function (AbstractSerializer $serializer, $model) {
return $serializer->hasOne($model, DiscussionSerializer::class, 'customSerializerRelation');
}),
(new Extend\ApiController(ShowUserController::class))
->addInclude('customSerializerRelation')
);
$response = $this->send(
$this->request('GET', '/api/users/2', [
'authenticatedAs' => 1,
])
);
$responseJson = json_decode($response->getBody(), true);
$this->assertArrayHasKey('customSerializerRelation', $responseJson['data']['relationships']);
$this->assertEquals('discussions', $responseJson['data']['relationships']['customSerializerRelation']['data']['type']);
}
/**
* @test
*/
public function custom_relationship_with_invokable_exists_if_added()
{
$this->extend(
(new Extend\Model(User::class))
->hasOne('customSerializerRelation', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(UserSerializer::class))
->relationship('customSerializerRelation', CustomRelationshipInvokableClass::class),
(new Extend\ApiController(ShowUserController::class))
->addInclude('customSerializerRelation')
(new Extend\ApiResource(UserResource::class))
->fields(fn () => [
Schema\Relationship\ToOne::make('customSerializerRelation')
->type('discussions')
->includable()
])
->endpoint(Show::class, function (Show $endpoint) {
return $endpoint->addDefaultInclude(['customSerializerRelation']);
})
);
$response = $this->send(
@@ -458,10 +303,15 @@ class ApiSerializerTest extends TestCase
$this->extend(
(new Extend\Model(User::class))
->hasMany('anotherCustomRelation', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(BasicUserSerializer::class))
->hasMany('anotherCustomRelation', DiscussionSerializer::class),
(new Extend\ApiController(ShowUserController::class))
->addInclude('anotherCustomRelation')
(new Extend\ApiResource(AbstractDatabaseResource::class))
->fields(fn () => [
Schema\Relationship\ToMany::make('anotherCustomRelation')
->type('discussions')
->includable()
])
->endpoint(Show::class, function (Show $endpoint) {
return $endpoint->addDefaultInclude(['anotherCustomRelation']);
})
);
$response = $this->send(
@@ -475,62 +325,15 @@ class ApiSerializerTest extends TestCase
$this->assertArrayHasKey('anotherCustomRelation', $responseJson['data']['relationships']);
$this->assertCount(3, $responseJson['data']['relationships']['anotherCustomRelation']['data']);
}
/**
* @test
*/
public function custom_relationship_prioritizes_child_classes()
{
$this->extend(
(new Extend\Model(User::class))
->hasOne('postCustomRelation', Post::class, 'user_id'),
(new Extend\Model(User::class))
->hasOne('discussionCustomRelation', Discussion::class, 'user_id'),
(new Extend\ApiSerializer(BasicUserSerializer::class))
->hasOne('postCustomRelation', PostSerializer::class),
(new Extend\ApiSerializer(UserSerializer::class))
->relationship('postCustomRelation', function (AbstractSerializer $serializer, $model) {
return $serializer->hasOne($model, DiscussionSerializer::class, 'discussionCustomRelation');
}),
(new Extend\ApiController(ShowUserController::class))
->addInclude('postCustomRelation')
);
$response = $this->send(
$this->request('GET', '/api/users/2', [
'authenticatedAs' => 1,
])
);
$responseJson = json_decode($response->getBody(), true);
$this->assertArrayHasKey('postCustomRelation', $responseJson['data']['relationships']);
$this->assertEquals('discussions', $responseJson['data']['relationships']['postCustomRelation']['data']['type']);
}
}
class CustomAttributesInvokableClass
{
public function __invoke()
public function __invoke(): array
{
return [
'customAttributeFromInvokable' => true
Schema\Boolean::make('customAttributeFromInvokable')
->get(fn () => true),
];
}
}
class CustomSingleAttributeInvokableClass
{
public function __invoke()
{
return true;
}
}
class CustomRelationshipInvokableClass
{
public function __invoke(AbstractSerializer $serializer, $model)
{
return $serializer->hasOne($model, DiscussionSerializer::class, 'customSerializerRelation');
}
}

View File

@@ -10,7 +10,8 @@
namespace Flarum\Tests\integration\extenders;
use Exception;
use Flarum\Api\Serializer\ForumSerializer;
use Flarum\Api\Resource\ForumResource;
use Flarum\Api\Schema\Boolean;
use Flarum\Extend;
use Flarum\Extension\ExtensionManager;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
@@ -25,14 +26,13 @@ class ConditionalTest extends TestCase
{
$this->extend(
(new Extend\Conditional())
->when(true, function () {
return [
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return ['customConditionalAttribute' => true];
})
];
})
->when(true, fn () => [
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Boolean::make('customConditionalAttribute')
->get(fn () => true)
])
])
);
$this->app();
@@ -53,14 +53,13 @@ class ConditionalTest extends TestCase
{
$this->extend(
(new Extend\Conditional())
->when(false, function () {
return [
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return ['customConditionalAttribute' => true];
})
];
})
->when(false, fn () => [
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Boolean::make('customConditionalAttribute')
->get(fn () => true)
])
])
);
$this->app();
@@ -81,16 +80,13 @@ class ConditionalTest extends TestCase
{
$this->extend(
(new Extend\Conditional())
->when(function () {
return true;
}, function () {
return [
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return ['customConditionalAttribute' => true];
})
];
})
->when(fn () => true, fn () => [
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Boolean::make('customConditionalAttribute')
->get(fn () => true)
])
])
);
$this->app();
@@ -111,16 +107,13 @@ class ConditionalTest extends TestCase
{
$this->extend(
(new Extend\Conditional())
->when(function () {
return false;
}, function () {
return [
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return ['customConditionalAttribute' => true];
})
];
})
->when(fn () => false, fn () => [
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Boolean::make('customConditionalAttribute')
->get(fn () => true)
])
])
);
$this->app();
@@ -147,14 +140,13 @@ class ConditionalTest extends TestCase
if (! $extensions) {
throw new Exception('ExtensionManager not injected');
}
}, function () {
return [
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return ['customConditionalAttribute' => true];
})
];
})
}, fn () => [
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Boolean::make('customConditionalAttribute')
->get(fn () => true)
])
])
);
$this->app();
@@ -294,14 +286,13 @@ class ConditionalTest extends TestCase
{
$this->extend(
(new Extend\Conditional())
->when(false, function () {
return [
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return ['customConditionalAttribute' => true];
})
];
})
->when(false, fn () => [
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Boolean::make('customConditionalAttribute')
->get(fn () => true)
])
])
);
$this->app();
@@ -322,14 +313,13 @@ class ConditionalTest extends TestCase
{
$this->extend(
(new Extend\Conditional())
->when(true, function () {
return [
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return ['customConditionalAttribute' => true];
})
];
})
->when(true, fn () => [
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Boolean::make('customConditionalAttribute')
->get(fn () => true)
])
])
);
$this->app();
@@ -370,12 +360,11 @@ class TestExtender
public function __invoke(): array
{
return [
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function () {
return [
'customConditionalAttribute' => true
];
})
(new Extend\ApiResource(ForumResource::class))
->fields(fn () => [
Boolean::make('customConditionalAttribute')
->get(fn () => true)
])
];
}
}

View File

@@ -9,33 +9,42 @@
namespace Flarum\Tests\integration\extenders;
use Flarum\Api\JsonApi;
use Flarum\Api\Resource\GroupResource;
use Flarum\Extend;
use Flarum\Foundation\Application;
use Flarum\Group\Command\CreateGroup;
use Flarum\Group\Event\Created;
use Flarum\Group\Group;
use Flarum\Locale\TranslatorInterface;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Flarum\User\User;
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
use Illuminate\Contracts\Events\Dispatcher;
class EventTest extends TestCase
{
use RetrievesAuthorizedUsers;
protected function buildGroup()
protected function buildGroup(): Group
{
$bus = $this->app()->getContainer()->make(BusDispatcher::class);
/** @var JsonApi $api */
$api = $this->app()->getContainer()->make(JsonApi::class);
return $bus->dispatch(
new CreateGroup(User::find(1), ['attributes' => [
'nameSingular' => 'test group',
'namePlural' => 'test groups',
'color' => '#000000',
'icon' => 'fas fa-crown',
]])
);
return $api->forResource(GroupResource::class)
->forEndpoint('create')
->process(
body: [
'data' => [
'attributes' => [
'nameSingular' => 'test group',
'namePlural' => 'test groups',
'color' => '#000000',
'icon' => 'fas fa-crown',
]
],
],
options: ['actor' => User::find(1)]
);
}
/**

View File

@@ -34,7 +34,11 @@ class MailTest extends TestCase
])
);
$fields = json_decode($response->getBody()->getContents(), true)['data']['attributes']['fields'];
$body = $response->getBody()->getContents();
$this->assertEquals(200, $response->getStatusCode(), $body);
$fields = json_decode($body, true)['data']['attributes']['fields'];
// The custom driver does not exist
$this->assertArrayNotHasKey('custom', $fields);

View File

@@ -19,6 +19,13 @@ class ModelPrivateTest extends TestCase
{
use RetrievesAuthorizedUsers;
protected function setUp(): void
{
parent::setUp();
Discussion::unguard();
}
/**
* @test
*/
@@ -28,8 +35,10 @@ class ModelPrivateTest extends TestCase
$user = User::find(1);
$discussion = Discussion::start('Some Discussion', $user);
$discussion->save();
$discussion = Discussion::create([
'title' => 'Some Discussion',
'user_id' => $user->id,
]);
$this->assertNull($discussion->is_private);
}
@@ -50,10 +59,14 @@ class ModelPrivateTest extends TestCase
$user = User::find(1);
$privateDiscussion = Discussion::start('Private Discussion', $user);
$publicDiscussion = Discussion::start('Public Discussion', $user);
$privateDiscussion->save();
$publicDiscussion->save();
$privateDiscussion = Discussion::create([
'title' => 'Private Discussion',
'user_id' => $user->id,
]);
$publicDiscussion = Discussion::create([
'title' => 'Public Discussion',
'user_id' => $user->id,
]);
$this->assertTrue($privateDiscussion->is_private);
$this->assertFalse($publicDiscussion->is_private);
@@ -73,10 +86,14 @@ class ModelPrivateTest extends TestCase
$user = User::find(1);
$privateDiscussion = Discussion::start('Private Discussion', $user);
$publicDiscussion = Discussion::start('Public Discussion', $user);
$privateDiscussion->save();
$publicDiscussion->save();
$privateDiscussion = Discussion::create([
'title' => 'Private Discussion',
'user_id' => $user->id,
]);
$publicDiscussion = Discussion::create([
'title' => 'Public Discussion',
'user_id' => $user->id,
]);
$this->assertTrue($privateDiscussion->is_private);
$this->assertFalse($publicDiscussion->is_private);
@@ -102,10 +119,14 @@ class ModelPrivateTest extends TestCase
$user = User::find(1);
$privateDiscussion = Discussion::start('Private Discussion', $user);
$publicDiscussion = Discussion::start('Public Discussion', $user);
$privateDiscussion->save();
$publicDiscussion->save();
$privateDiscussion = Discussion::create([
'title' => 'Private Discussion',
'user_id' => $user->id,
]);
$publicDiscussion = Discussion::create([
'title' => 'Public Discussion',
'user_id' => $user->id,
]);
$this->assertTrue($privateDiscussion->is_private);
$this->assertFalse($publicDiscussion->is_private);

View File

@@ -306,11 +306,13 @@ class ModelTest extends TestCase
$this->app();
$group1 = new Group;
$group2 = new Group;
Group::boot();
$this->assertEquals(1, $group1->counter);
$this->assertEquals(3, $group2->counter);
$group1 = new Group();
$group2 = new Group();
$this->assertEquals(3, $group1->counter);
$this->assertEquals(4, $group2->counter);
}
/**

View File

@@ -31,19 +31,6 @@ class NotificationTest extends TestCase
$this->assertArrayNotHasKey('customNotificationType', Notification::getSubjectModels());
}
/**
* @test
*/
public function notification_serializer_doesnt_exist_by_default()
{
$this->app();
$this->assertNotContains(
'customNotificationTypeSerializer',
$this->app->getContainer()->make('flarum.api.notification_serializers')
);
}
/**
* @test
*/
@@ -57,34 +44,13 @@ class NotificationTest extends TestCase
*/
public function notification_type_exists_if_added()
{
$this->extend((new Extend\Notification)->type(
CustomNotificationType::class,
'customNotificationTypeSerializer'
));
$this->extend((new Extend\Notification)->type(CustomNotificationType::class));
$this->app();
$this->assertArrayHasKey('customNotificationType', Notification::getSubjectModels());
}
/**
* @test
*/
public function notification_serializer_exists_if_added()
{
$this->extend((new Extend\Notification)->type(
CustomNotificationType::class,
'customNotificationTypeSerializer'
));
$this->app();
$this->assertContains(
'customNotificationTypeSerializer',
$this->app->getContainer()->make('flarum.api.notification_serializers')
);
}
/**
* @test
*/
@@ -107,9 +73,9 @@ class NotificationTest extends TestCase
{
$this->extend(
(new Extend\Notification())
->type(CustomNotificationType::class, 'customSerializer')
->type(SecondCustomNotificationType::class, 'secondCustomSerializer', ['customDriver'])
->type(ThirdCustomNotificationType::class, 'thirdCustomSerializer')
->type(CustomNotificationType::class)
->type(SecondCustomNotificationType::class, ['customDriver'])
->type(ThirdCustomNotificationType::class)
->driver('customDriver', CustomNotificationDriver::class, [CustomNotificationType::class])
->driver('secondCustomDriver', SecondCustomNotificationDriver::class, [SecondCustomNotificationType::class])
);
@@ -132,7 +98,7 @@ class NotificationTest extends TestCase
{
$this->extend(
(new Extend\Notification)
->type(CustomNotificationType::class, 'customNotificationTypeSerializer')
->type(CustomNotificationType::class)
->driver('customNotificationDriver', CustomNotificationDriver::class)
->beforeSending(function ($blueprint, $users) {
if ($blueprint instanceof CustomNotificationType) {

View File

@@ -42,13 +42,18 @@ class SearchIndexTest extends TestCase
public static function modelProvider(): array
{
return [
['discussions', Discussion::class, 'title'],
['posts', CommentPost::class, 'content'],
['discussions', Discussion::class, [
'title' => 'test',
'content' => 'test!',
]],
['posts', CommentPost::class, [
'content' => 'test!!',
]],
];
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_create(string $type, string $modelClass, string $attribute)
public function test_indexer_triggered_on_create(string $type, string $modelClass, array $attributes)
{
$this->extend(
(new Extend\SearchIndex())
@@ -61,9 +66,8 @@ class SearchIndexTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'attributes' => [
$attribute => 'test',
],
'type' => $type,
'attributes' => $attributes,
'relationships' => ($type === 'posts' ? [
'discussion' => [
'data' => [
@@ -71,7 +75,7 @@ class SearchIndexTest extends TestCase
'id' => 1,
],
],
] : null),
] : []),
]
],
]),
@@ -81,7 +85,7 @@ class SearchIndexTest extends TestCase
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_save(string $type, string $modelClass, string $attribute)
public function test_indexer_triggered_on_save(string $type, string $modelClass, array $attributes)
{
$this->extend(
(new Extend\SearchIndex())
@@ -94,9 +98,8 @@ class SearchIndexTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'attributes' => [
$attribute => 'changed'
]
'type' => $type,
'attributes' => $type === 'discussions' ? array_diff_key($attributes, ['content' => null]) : $attributes,
]
],
]),
@@ -106,7 +109,7 @@ class SearchIndexTest extends TestCase
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_delete(string $type, string $modelClass, string $attribute)
public function test_indexer_triggered_on_delete(string $type, string $modelClass, array $attributes)
{
$this->extend(
(new Extend\SearchIndex())
@@ -125,7 +128,7 @@ class SearchIndexTest extends TestCase
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_hide(string $type, string $modelClass, string $attribute)
public function test_indexer_triggered_on_hide(string $type, string $modelClass, array $attributes)
{
$this->extend(
(new Extend\SearchIndex())
@@ -138,6 +141,7 @@ class SearchIndexTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => $type,
'attributes' => [
'isHidden' => true
]
@@ -150,7 +154,7 @@ class SearchIndexTest extends TestCase
}
/** @dataProvider modelProvider */
public function test_indexer_triggered_on_restore(string $type, string $modelClass, string $attribute)
public function test_indexer_triggered_on_restore(string $type, string $modelClass, array $attributes)
{
$this->extend(
(new Extend\SearchIndex())
@@ -163,6 +167,7 @@ class SearchIndexTest extends TestCase
'authenticatedAs' => 1,
'json' => [
'data' => [
'type' => $type,
'attributes' => [
'isHidden' => false
]

View File

@@ -97,6 +97,30 @@ class SettingsTest extends TestCase
$this->assertEquals('customValueModified', $payload['data']['attributes']['customPrefix.customSetting']);
}
/**
* @test
*/
public function custom_setting_callback_can_cast_to_type()
{
$this->extend(
(new Extend\Settings())
->serializeToForum('customPrefix.customSetting', 'custom-prefix.custom_setting', function ($value) {
return (bool) $value;
})
);
$response = $this->send(
$this->request('GET', '/api', [
'authenticatedAs' => 1,
])
);
$payload = json_decode($response->getBody()->getContents(), true);
$this->assertArrayHasKey('customPrefix.customSetting', $payload['data']['attributes']);
$this->assertEquals(true, $payload['data']['attributes']['customPrefix.customSetting']);
}
/**
* @test
*/

View File

@@ -149,7 +149,7 @@ class ThemeTest extends TestCase
$response = $this->send($this->request('GET', '/'));
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, $response->getStatusCode(), $response->getBody()->getContents());
$cssFilePath = $this->app()->getContainer()->make('filesystem')->disk('flarum-assets')->path('forum.css');
$contents = file_get_contents($cssFilePath);

View File

@@ -10,16 +10,16 @@
namespace Flarum\Tests\integration\extenders;
use Flarum\Extend;
use Flarum\Group\GroupValidator;
use Flarum\Foundation\AbstractValidator;
use Flarum\Testing\integration\TestCase;
use Flarum\User\UserValidator;
use Flarum\User\User;
use Illuminate\Validation\ValidationException;
class ValidatorTest extends TestCase
{
private function extendToRequireLongPassword()
{
$this->extend((new Extend\Validator(UserValidator::class))->configure(function ($flarumValidator, $validator) {
$this->extend((new Extend\Validator(CustomUserValidator::class))->configure(function ($flarumValidator, $validator) {
$validator->setRules([
'password' => [
'required',
@@ -31,7 +31,7 @@ class ValidatorTest extends TestCase
private function extendToRequireLongPasswordViaInvokableClass()
{
$this->extend((new Extend\Validator(UserValidator::class))->configure(CustomValidatorClass::class));
$this->extend((new Extend\Validator(CustomUserValidator::class))->configure(CustomValidatorClass::class));
}
/**
@@ -39,7 +39,7 @@ class ValidatorTest extends TestCase
*/
public function custom_validation_rule_does_not_exist_by_default()
{
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
$this->app()->getContainer()->make(CustomUserValidator::class)->assertValid(['password' => 'simplePassword']);
// If we have gotten this far, no validation exception has been thrown, so the test is succesful.
$this->assertTrue(true);
@@ -54,7 +54,7 @@ class ValidatorTest extends TestCase
$this->expectException(ValidationException::class);
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
$this->app()->getContainer()->make(CustomUserValidator::class)->assertValid(['password' => 'simplePassword']);
}
/**
@@ -66,7 +66,7 @@ class ValidatorTest extends TestCase
$this->expectException(ValidationException::class);
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
$this->app()->getContainer()->make(CustomUserValidator::class)->assertValid(['password' => 'simplePassword']);
}
/**
@@ -76,7 +76,7 @@ class ValidatorTest extends TestCase
{
$this->extendToRequireLongPassword();
$this->app()->getContainer()->make(GroupValidator::class)->assertValid(['password' => 'simplePassword']);
$this->app()->getContainer()->make(CustomValidator::class)->assertValid(['password' => 'simplePassword']);
// If we have gotten this far, no validation exception has been thrown, so the test is succesful.
$this->assertTrue(true);
@@ -95,3 +95,57 @@ class CustomValidatorClass
] + $validator->getRules());
}
}
class CustomUserValidator extends AbstractValidator
{
protected ?User $user = null;
public function getUser(): ?User
{
return $this->user;
}
public function setUser(User $user): void
{
$this->user = $user;
}
protected function getRules(): array
{
$idSuffix = $this->user ? ','.$this->user->id : '';
return [
'username' => [
'required',
'regex:/^[a-z0-9_-]+$/i',
'unique:users,username'.$idSuffix,
'min:3',
'max:30'
],
'email' => [
'required',
'email:filter',
'unique:users,email'.$idSuffix
],
'password' => [
'required',
'min:8'
]
];
}
protected function getMessages(): array
{
return [
'username.regex' => $this->translator->trans('core.api.invalid_username_message')
];
}
}
class CustomValidator extends AbstractValidator
{
protected array $rules = [
'name_singular' => ['required'],
'name_plural' => ['required']
];
}

View File

@@ -34,10 +34,11 @@ class RegisterTest extends TestCase
$this->request('POST', '/register')
);
$this->assertEquals(422, $response->getStatusCode());
$body = (string) $response->getBody();
$this->assertEquals(422, $response->getStatusCode(), $body);
// The response body should contain details about the failed validation
$body = (string) $response->getBody();
$this->assertJson($body);
$this->assertEquals([
'errors' => [

View File

@@ -10,10 +10,10 @@
namespace Flarum\Tests\integration\policy;
use Carbon\Carbon;
use Flarum\Bus\Dispatcher;
use Flarum\Api\JsonApi;
use Flarum\Api\Resource\PostResource;
use Flarum\Discussion\Discussion;
use Flarum\Foundation\DispatchEventsTrait;
use Flarum\Post\Command\PostReply;
use Flarum\Post\Post;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
@@ -94,9 +94,30 @@ class DiscussionPolicyTest extends TestCase
$this->assertTrue($user->can('rename', $discussion));
$this->assertFalse($user->can('rename', $discussionWithReply));
$this->app()->getContainer()->make(Dispatcher::class)->dispatch(
new PostReply(1, User::findOrFail(1), ['attributes' => ['content' => 'test']], null)
);
/** @var JsonApi $api */
$api = $this->app()->getContainer()->make(JsonApi::class);
$api
->forResource(PostResource::class)
->forEndpoint('create')
->process(
body: [
'data' => [
'attributes' => [
'content' => 'test'
],
'relationships' => [
'discussion' => [
'data' => [
'type' => 'discussions',
'id' => '1'
],
],
],
],
],
options: ['actor' => User::findOrFail(1)]
);
// Date further into the future
Carbon::setTestNow('2025-01-01 13:00:00');