Skip to content

Commit

Permalink
fix: /all not recognized as discussion route when a different route…
Browse files Browse the repository at this point in the history
… is default (#60)

* chore: add middleware and filter tests

* Apply fixes from StyleCI

* typo

* params

* Apply fixes from StyleCI

* query

* fix: /all not recognized as discussion route when a different route is default

* Apply fixes from StyleCI

---------

Co-authored-by: StyleCI Bot <bot@styleci.io>
  • Loading branch information
imorland and StyleCIBot authored Feb 11, 2025
1 parent 8aea48e commit 54f76ff
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 12 deletions.
35 changes: 23 additions & 12 deletions src/Middleware/AddLanguageFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,32 +104,43 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
*/
protected function addQueryParams(ServerRequestInterface $request, array $params, string $language): ServerRequestInterface
{
// use recursive merge to preserve filters added by other extensions
$newParams = array_merge_recursive($params, ['filter' => ['language' => $language]]);
// Remove the top-level 'language' parameter so it isn’t passed along.
unset($params['language']);

// If a search is in progress, add the search gambit
if (Arr::get($params, 'q')) {
$newParams['q'] = Arr::get($params, 'q').' language:'.$language;
// Ensure the 'filter' key exists as an array.
if (!isset($params['filter']) || !is_array($params['filter'])) {
$params['filter'] = [];
}

return $request->withQueryParams($newParams);
// Set or overwrite the language filter.
$params['filter']['language'] = $language;

// If a search query is present, append the language filter to it.
if (isset($params['q']) && $params['q'] !== '') {
$params['q'] = $params['q'].' language:'.$language;
}

return $request->withQueryParams($params);
}

protected function isDiscussionListPath(ServerRequestInterface $request)
{
$path = $request->getAttribute('originalUri')->getPath();

// Check for the 'index' route (showing all discussions)
$defaultRoute = $this->settings->get('default_route');

if ($defaultRoute === '/all') {
if ($path === '/') {
// If the default route is '/all', treat both '/' and '/all' as the discussion list.
if ($path === '/' || $path === '/all') {
return true;
}
} else {
// Otherwise, if the default route is not '/all', then '/all' is the discussion list.
if ($path === '/all') {
return true;
}
} elseif ($path === '/all') {
return true;
}

// Check for the 'tag' route (tag page)
// Also apply for tag pages.
if (substr($path, 0, 2) === '/t') {
return true;
}
Expand Down
219 changes: 219 additions & 0 deletions tests/integration/api/MiddlewareLanguageFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
<?php

/*
* This file is part of fof/discussion-language.
*
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FoF\DiscussionLanguage\Tests\integration\api;

use Carbon\Carbon;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;

class MiddlewareLanguageFilterTest extends TestCase
{
use RetrievesAuthorizedUsers;

public function setUp(): void
{
parent::setUp();

$this->setting('default_route', '/all');

$this->extension('flarum-tags');
$this->extension('fof-discussion-language');

$this->prepareDatabase([
'discussions' => [
['id' => 1, 'title' => 'English discussion', 'created_at' => Carbon::now(), 'user_id' => 2, 'first_post_id' => 1, 'language_id' => 1],
['id' => 2, 'title' => 'German discussion', 'created_at' => Carbon::now(), 'user_id' => 2, 'first_post_id' => 2, 'language_id' => 2],
['id' => 3, 'title' => 'French discussion', 'created_at' => Carbon::now(), 'user_id' => 2, 'first_post_id' => 3, 'language_id' => 3],
],
'discussion_languages' => [
['id' => 1, 'code' => 'en', 'country' => 'GB'],
['id' => 2, 'code' => 'de', 'country' => 'DE'],
['id' => 3, 'code' => 'fr', 'country' => 'FR'],
],
'discussion_tag' => [
['discussion_id' => 1, 'tag_id' => 1, 'created_at' => Carbon::now()],
['discussion_id' => 2, 'tag_id' => 1, 'created_at' => Carbon::now()],
['discussion_id' => 3, 'tag_id' => 1, 'created_at' => Carbon::now()],
],
'posts' => [
['id' => 1, 'discussion_id' => 1, 'content' => 'English post', 'user_id' => 2, 'created_at' => Carbon::now()],
['id' => 2, 'discussion_id' => 2, 'content' => 'German post', 'user_id' => 2, 'created_at' => Carbon::now()],
['id' => 3, 'discussion_id' => 3, 'content' => 'French post', 'user_id' => 2, 'created_at' => Carbon::now()],
],
'tags' => [
['id' => 1, 'name' => 'Tag 1', 'slug' => 'tag-1'],
],
'users' => [
$this->normalUser(),
],
]);
}

/**
* Extracts the JSON payload embedded in the Flarum HTML response.
*
* This function looks for the <script> tag with id "flarum-json-payload"
* and returns its decoded JSON content.
*
* @param string $html
*
* @return array
*/
protected function extractJsonPayload(string $html): array
{
// Look for the script tag that contains the JSON payload.
if (preg_match('/<script id="flarum-json-payload" type="application\/json">(.*?)<\/script>/s', $html, $matches)) {
$json = trim($matches[1]);
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$this->fail('Invalid JSON in flarum-json-payload: '.json_last_error_msg());
}

return $data;
}
$this->fail('Could not extract JSON payload from HTML');
}

public function languages(): array
{
return [
'en' => 'English discussion',
'de' => 'German discussion',
'fr' => 'French discussion',
];
}

public function routesProvider(): array
{
return [
[''],
['all'],
['t/tag-1'],

];
}

/**
* @test
*
* @dataProvider routesProvider
*/
public function all_discussions_are_returned_when_no_query_is_included_frontend(string $route)
{
$response = $this->send(
$this->request('GET', "/$route")
);

$this->assertEquals(200, $response->getStatusCode());

$body = $response->getBody()->getContents();

// Extract the JSON payload from the HTML.
$payload = $this->extractJsonPayload($body);

$this->assertArrayHasKey('apiDocument', $payload, 'apiDocument key missing from payload');
$apiDocument = $payload['apiDocument'];
$this->assertArrayHasKey('data', $apiDocument, 'Data key missing in apiDocument');

// Assert that there are three discussions returned.
$this->assertCount(3, $apiDocument['data'], 'The number of discussions returned does not match the expectation.');

$titles = array_map(function ($discussion) {
return $discussion['attributes']['title'] ?? '';
}, $apiDocument['data']);

$this->assertContains('English discussion', $titles);
$this->assertContains('German discussion', $titles);
$this->assertContains('French discussion', $titles);

// print_r($payload);
}

public function all_discussions_are_returned_from_the_api_when_no_query_is_included()
{
$response = $this->send(
$this->request('GET', '/api/discussions')
);

$this->assertEquals(200, $response->getStatusCode());

$payload = json_decode($response->getBody()->getContents(), true);

$this->assertArrayHasKey('data', $payload);
$this->assertCount(3, $payload['data']);

$titles = array_map(function ($discussion) {
return $discussion['attributes']['title'] ?? '';
}, $payload['data']);

$this->assertContains('English discussion', $titles);
$this->assertContains('German discussion', $titles);
$this->assertContains('French discussion', $titles);
}

/**
* @test
*
* @dataProvider routesProvider
*
* Validate that supplying a language parameter filters the discussions to return only the expected discussion.
*/
public function discussions_are_filtered_by_language_parameter_frontend(string $route)
{
$languages = $this->languages();

foreach ($languages as $code => $expectedTitle) {
$response = $this->send(
$this->request('GET', "/$route")->withQueryParams(['language' => $code])
);

$this->assertEquals(200, $response->getStatusCode(), "Unexpected status code for language $code");

$body = $response->getBody()->getContents();
$payload = $this->extractJsonPayload($body);

$this->assertArrayHasKey('apiDocument', $payload, 'apiDocument key missing from payload');
$apiDocument = $payload['apiDocument'];
$this->assertArrayHasKey('data', $apiDocument, 'Data key missing in apiDocument');

$discussions = $apiDocument['data'];
$this->assertCount(1, $discussions, "For language '$code', expected exactly one discussion.");

$title = $discussions[0]['attributes']['title'] ?? '';
$this->assertEquals($expectedTitle, $title, "For language '$code', expected discussion title '$expectedTitle', got '$title'.");
}
}

/**
* @test
*/
public function discussions_are_filtered_by_language_parameter_api()
{
$languages = $this->languages();

foreach ($languages as $code => $expectedTitle) {
$response = $this->send(
$this->request('GET', '/api/discussions')->withQueryParams(['filter' => ['language' => $code]])
);

$this->assertEquals(200, $response->getStatusCode(), "Unexpected status code for language $code");

$payload = json_decode($response->getBody()->getContents(), true);

$this->assertArrayHasKey('data', $payload);
$this->assertCount(1, $payload['data'], "For language '$code', expected exactly one discussion.");

$title = $payload['data'][0]['attributes']['title'] ?? '';
$this->assertEquals($expectedTitle, $title, "For language '$code', expected discussion title '$expectedTitle', got '$title'.");
}
}
}

0 comments on commit 54f76ff

Please sign in to comment.