Move in visibility scoping from tags, add automated testing.

This commit is contained in:
Alexander Skvortsov 2021-05-11 20:04:52 -04:00
parent f80a88d45b
commit be59e77a01
13 changed files with 592 additions and 38 deletions

View File

@ -0,0 +1,78 @@
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php: [7.3, 7.4, '8.0']
service: ['mysql:5.7', mariadb]
prefix: ['', flarum_]
include:
- service: 'mysql:5.7'
db: MySQL
- service: mariadb
db: MariaDB
- prefix: flarum_
prefixStr: (prefix)
exclude:
- php: 7.3
service: 'mysql:5.7'
prefix: flarum_
- php: 7.3
service: mariadb
prefix: flarum_
- php: 8.0
service: 'mysql:5.7'
prefix: flarum_
- php: 8.0
service: mariadb
prefix: flarum_
services:
mysql:
image: ${{ matrix.service }}
ports:
- 13306:3306
name: 'PHP ${{ matrix.php }} / ${{ matrix.db }} ${{ matrix.prefixStr }}'
steps:
- uses: actions/checkout@master
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip
tools: phpunit, composer:v2
# The authentication alter is necessary because newer mysql versions use the `caching_sha2_password` driver,
# which isn't supported prior to PHP7.4
# When we drop support for PHP7.3, we should remove this from the setup.
- name: Create MySQL Database
run: |
sudo systemctl start mysql
mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 13306
mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';" --port 13306
- name: Install Composer dependencies
run: composer install
- name: Setup Composer tests
run: composer test:setup
env:
DB_PORT: 13306
DB_PASSWORD: root
DB_PREFIX: ${{ matrix.prefix }}
- name: Run Composer tests
run: composer test
env:
COMPOSER_PROCESS_TIMEOUT: 600

22
extensions/flags/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9000
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 9000
}
]
}

View File

@ -1,41 +1,63 @@
{
"name": "flarum/flags",
"description": "Allow users to flag posts for moderator review.",
"type": "flarum-extension",
"keywords": ["moderation"],
"license": "MIT",
"support": {
"issues": "https://github.com/flarum/core/issues",
"source": "https://github.com/flarum/flags",
"forum": "https://discuss.flarum.org"
},
"homepage": "https://flarum.org",
"funding": [
{
"type": "website",
"url": "https://flarum.org/donate/"
}
],
"require": {
"flarum/core": "^0.1.0-beta.16"
},
"autoload": {
"psr-4": {
"Flarum\\Flags\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.1.x-dev"
},
"flarum-extension": {
"title": "Flags",
"category": "feature",
"icon": {
"name": "fas fa-flag",
"backgroundColor": "#D659B5",
"color": "#fff"
}
}
"name": "flarum/flags",
"description": "Allow users to flag posts for moderator review.",
"type": "flarum-extension",
"keywords": [
"moderation"
],
"license": "MIT",
"support": {
"issues": "https://github.com/flarum/core/issues",
"source": "https://github.com/flarum/flags",
"forum": "https://discuss.flarum.org"
},
"homepage": "https://flarum.org",
"funding": [
{
"type": "website",
"url": "https://flarum.org/donate/"
}
],
"require": {
"flarum/core": "^0.1.0-beta.16"
},
"autoload": {
"psr-4": {
"Flarum\\Flags\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "0.1.x-dev"
},
"flarum-extension": {
"title": "Flags",
"category": "feature",
"icon": {
"name": "fas fa-flag",
"backgroundColor": "#D659B5",
"color": "#fff"
}
}
},
"scripts": {
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "phpunit -c tests/phpunit.unit.xml",
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"test:setup": "@php tests/integration/setup.php"
},
"scripts-descriptions": {
"test": "Runs all tests.",
"test:unit": "Runs all unit tests.",
"test:integration": "Runs all integration tests.",
"test:setup": "Sets up a database for use with integration tests. Execute this only once."
},
"require-dev": {
"flarum/core": "0.1.x-dev",
"flarum/tags": "0.1.x-dev",
"flarum/testing": "*@dev"
}
}

View File

@ -15,6 +15,7 @@ use Flarum\Api\Serializer\CurrentUserSerializer;
use Flarum\Api\Serializer\ForumSerializer;
use Flarum\Api\Serializer\PostSerializer;
use Flarum\Extend;
use Flarum\Flags\Access\ScopeFlagVisibility;
use Flarum\Flags\AddCanFlagAttribute;
use Flarum\Flags\AddFlagsApiAttributes;
use Flarum\Flags\AddNewFlagCountAttribute;
@ -78,5 +79,8 @@ return [
(new Extend\Event())
->listen(Deleted::class, Listener\DeleteFlags::class),
(new Extend\ModelVisibility(Flag::class))
->scope(ScopeFlagVisibility::class),
new Extend\Locales(__DIR__.'/locale'),
];

View File

@ -0,0 +1,59 @@
<?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\Flags\Access;
use Flarum\Extension\ExtensionManager;
use Flarum\Tags\Tag;
use Flarum\User\User;
use Illuminate\Database\Eloquent\Builder;
class ScopeFlagVisibility
{
/**
* @var ExtensionManager
*/
protected $extensions;
public function __construct(ExtensionManager $extensions)
{
$this->extensions = $extensions;
}
public function __invoke(User $actor, Builder $query)
{
if ($this->extensions->isEnabled('flarum-tags')) {
$query
->select('flags.*')
->leftJoin('posts', 'posts.id', '=', 'flags.post_id')
->leftJoin('discussions', 'discussions.id', '=', 'posts.discussion_id')
->whereNotExists(function ($query) use ($actor) {
return $query->selectRaw('1')
->from('discussion_tag')
->whereNotIn('tag_id', function ($query) use ($actor) {
Tag::query()->setQuery($query->from('tags'))->whereHasPermission($actor, 'discussion.viewFlags')->select('tags.id');
})
->whereColumn('discussions.id', 'discussion_id');
});
if (!$actor->hasPermission('discussion.viewFlags')) {
$query->whereExists(function ($query) use ($actor) {
return $query->selectRaw('1')
->from('discussion_tag')
->whereColumn('discussions.id', 'discussion_id');
});
}
}
if (!$actor->hasPermission('discussion.viewFlags')) {
$query->orWhere('flags.user_id', $actor->id);
}
}
}

View File

@ -0,0 +1 @@
C:37:"PHPUnit\Runner\DefaultTestResultCache":2499:{a:2:{s:7:"defects";a:13:{s:81:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::admin_can_see_all_flag";i:3;s:86:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::regular_user_sees_own_flags";i:3;s:77:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::mod_sees_all_flags";i:3;s:79:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::guest_cant_see_flags";i:4;s:73:"Flarum\Flags\Tests\integration\api\flags\ListTest::admin_can_see_all_flag";i:3;s:78:"Flarum\Flags\Tests\integration\api\flags\ListTest::regular_user_sees_own_flags";i:3;s:92:"Flarum\Flags\Tests\integration\api\flags\ListTest::mod_sees_own_flags_in_visible_discussions";i:4;s:71:"Flarum\Flags\Tests\integration\api\flags\ListTest::guest_cant_see_flags";i:3;s:69:"Flarum\Flags\Tests\integration\api\flags\ListTest::mod_sees_all_flags";i:3;s:82:"Flarum\Flags\Tests\integration\api\flags\ListTest::admin_can_see_one_flag_per_post";i:4;s:80:"Flarum\Flags\Tests\integration\api\flags\ListTest::mod_can_see_one_flag_per_post";i:3;s:90:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::admin_can_see_one_flag_per_post";i:4;s:88:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::mod_can_see_one_flag_per_post";i:3;}s:5:"times";a:13:{s:81:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::admin_can_see_all_flag";d:0.346;s:86:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::regular_user_sees_own_flags";d:0.2;s:77:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::mod_sees_all_flags";d:0.245;s:79:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::guest_cant_see_flags";d:0.134;s:73:"Flarum\Flags\Tests\integration\api\flags\ListTest::admin_can_see_all_flag";d:2.598;s:78:"Flarum\Flags\Tests\integration\api\flags\ListTest::regular_user_sees_own_flags";d:0.183;s:92:"Flarum\Flags\Tests\integration\api\flags\ListTest::mod_sees_own_flags_in_visible_discussions";d:0.139;s:71:"Flarum\Flags\Tests\integration\api\flags\ListTest::guest_cant_see_flags";d:0.112;s:69:"Flarum\Flags\Tests\integration\api\flags\ListTest::mod_sees_all_flags";d:0.364;s:82:"Flarum\Flags\Tests\integration\api\flags\ListTest::admin_can_see_one_flag_per_post";d:0.194;s:80:"Flarum\Flags\Tests\integration\api\flags\ListTest::mod_can_see_one_flag_per_post";d:0.177;s:90:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::admin_can_see_one_flag_per_post";d:0.24;s:88:"Flarum\Flags\Tests\integration\api\flags\ListWithTagsTest::mod_can_see_one_flag_per_post";d:0.235;}}}

View File

View File

@ -0,0 +1,133 @@
<?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\Flags\Tests\integration\api\flags;
use Flarum\Group\Group;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Illuminate\Support\Arr;
class ListTest extends TestCase
{
use RetrievesAuthorizedUsers;
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->extension('flarum-flags');
$this->prepareDatabase([
'users' => [
$this->normalUser(),
[
'id' => 3,
'username' => 'mod',
'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', // BCrypt hash for "too-obscure"
'email' => 'normal2@machine.local',
'is_email_confirmed' => 1,
]
],
'group_user' => [
['group_id' => Group::MODERATOR_ID, 'user_id' => 3]
],
'group_permission' => [
['group_id' => Group::MODERATOR_ID, 'permission' => 'discussion.viewFlags'],
],
'discussions' => [
['id' => 1, 'title' => '', 'user_id' => 1, 'comment_count' => 1],
],
'posts' => [
['id' => 1, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
['id' => 2, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
['id' => 3, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
],
'flags' => [
['id' => 1, 'post_id' => 1, 'user_id' => 1],
['id' => 2, 'post_id' => 1, 'user_id' => 2],
['id' => 3, 'post_id' => 1, 'user_id' => 3],
['id' => 4, 'post_id' => 2, 'user_id' => 2],
['id' => 5, 'post_id' => 3, 'user_id' => 1],
]
]);
}
/**
* @test
*/
public function admin_can_see_one_flag_per_post()
{
$response = $this->send(
$this->request('GET', '/api/flags', [
'authenticatedAs' => 1
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$ids = Arr::pluck($data, 'id');
$this->assertEqualsCanonicalizing(['1', '4', '5'], $ids);
}
/**
* @test
*/
public function regular_user_sees_own_flags()
{
$response = $this->send(
$this->request('GET', '/api/flags', [
'authenticatedAs' => 2
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$ids = Arr::pluck($data, 'id');
$this->assertEqualsCanonicalizing(['2', '4'], $ids);
}
/**
* @test
*/
public function mod_can_see_one_flag_per_post()
{
$response = $this->send(
$this->request('GET', '/api/flags', [
'authenticatedAs' => 3
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$ids = Arr::pluck($data, 'id');
$this->assertEqualsCanonicalizing(['1', '4', '5'], $ids);
}
/**
* @test
*/
public function guest_cant_see_flags()
{
$response = $this->send(
$this->request('GET', '/api/flags')
);
$this->assertEquals(401, $response->getStatusCode());
}
}

View File

@ -0,0 +1,168 @@
<?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\Flags\Tests\integration\api\flags;
use Flarum\Group\Group;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Illuminate\Support\Arr;
class ListWithTagsTest extends TestCase
{
use RetrievesAuthorizedUsers;
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->extension('flarum-flags');
$this->extension('flarum-tags');
$this->prepareDatabase([
'tags' => [
['id' => 1, 'name' => 'Unrestricted', 'slug' => '1', 'position' => 0, 'parent_id' => null],
['id' => 2, 'name' => 'Mods can view discussions', 'slug' => '2', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
['id' => 3, 'name' => 'Mods can view flags', 'slug' => '3', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
['id' => 4, 'name' => 'Mods can view discussions and flags', 'slug' => '4', 'position' => 0, 'parent_id' => null, 'is_restricted' => true],
],
'users' => [
$this->normalUser(),
[
'id' => 3,
'username' => 'mod',
'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', // BCrypt hash for "too-obscure"
'email' => 'normal2@machine.local',
'is_email_confirmed' => 1,
]
],
'group_user' => [
['group_id' => Group::MODERATOR_ID, 'user_id' => 3]
],
'group_permission' => [
['group_id' => Group::MODERATOR_ID, 'permission' => 'discussion.viewFlags'],
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag2.viewDiscussions'],
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag3.discussion.viewFlags'],
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag4.viewDiscussions'],
['group_id' => Group::MODERATOR_ID, 'permission' => 'tag4.discussion.viewFlags'],
],
'discussions' => [
['id' => 1, 'title' => 'no tags', 'user_id' => 1, 'comment_count' => 1],
['id' => 2, 'title' => 'has tags where mods can view discussions but not flags', 'user_id' => 1, 'comment_count' => 1],
['id' => 3, 'title' => 'has tags where mods can view flags but not discussions', 'user_id' => 1, 'comment_count' => 1],
['id' => 4, 'title' => 'has tags where mods can view discussions and flags', 'user_id' => 1, 'comment_count' => 1],
['id' => 5, 'title' => 'has unrestricted tag', 'user_id' => 1, 'comment_count' => 1],
],
'discussion_tag' => [
['discussion_id' => 2, 'tag_id' => 2],
['discussion_id' => 3, 'tag_id' => 3],
['discussion_id' => 4, 'tag_id' => 4],
['discussion_id' => 5, 'tag_id' => 1],
],
'posts' => [
// From regular ListTest
['id' => 1, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
['id' => 2, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
['id' => 3, 'discussion_id' => 1, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
// In tags
['id' => 4, 'discussion_id' => 2, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
['id' => 5, 'discussion_id' => 3, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
['id' => 6, 'discussion_id' => 4, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
['id' => 7, 'discussion_id' => 5, 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p></p></t>'],
],
'flags' => [
// From regular ListTest
['id' => 1, 'post_id' => 1, 'user_id' => 1],
['id' => 2, 'post_id' => 1, 'user_id' => 2],
['id' => 3, 'post_id' => 1, 'user_id' => 3],
['id' => 4, 'post_id' => 2, 'user_id' => 2],
['id' => 5, 'post_id' => 3, 'user_id' => 1],
// In tags
['id' => 6, 'post_id' => 4, 'user_id' => 1],
['id' => 7, 'post_id' => 5, 'user_id' => 1],
['id' => 8, 'post_id' => 6, 'user_id' => 1],
['id' => 9, 'post_id' => 7, 'user_id' => 1],
]
]);
}
/**
* @test
*/
public function admin_can_see_one_flag_per_post()
{
$response = $this->send(
$this->request('GET', '/api/flags', [
'authenticatedAs' => 1
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$ids = Arr::pluck($data, 'id');
$this->assertEqualsCanonicalizing(['1', '4', '5', '6', '7', '8', '9'], $ids);
}
/**
* @test
*/
public function regular_user_sees_own_flags()
{
$response = $this->send(
$this->request('GET', '/api/flags', [
'authenticatedAs' => 2
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$ids = Arr::pluck($data, 'id');
$this->assertEqualsCanonicalizing(['2', '4'], $ids);
}
/**
* @test
*/
public function mod_can_see_one_flag_per_post()
{
$response = $this->send(
$this->request('GET', '/api/flags', [
'authenticatedAs' => 3
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true)['data'];
$ids = Arr::pluck($data, 'id');
// 7 is included, even though mods can't view discussions.
// This is because the UI doesnt allow discussions.viewFlags without viewDiscussions.
$this->assertEqualsCanonicalizing(['1', '4', '5', '7', '8', '9'], $ids);
}
/**
* @test
*/
public function guest_cant_see_flags()
{
$response = $this->send(
$this->request('GET', '/api/flags')
);
$this->assertEquals(401, $response->getStatusCode());
}
}

View File

@ -0,0 +1,16 @@
<?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.
*/
use Flarum\Testing\integration\Setup\SetupScript;
require __DIR__.'/../../vendor/autoload.php';
$setup = new SetupScript();
$setup->run();

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="true"
stopOnFailure="false"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flarum Integration Tests">
<directory suffix="Test.php">./integration</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flarum Unit Tests">
<directory suffix="Test.php">./unit</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="\Mockery\Adapter\Phpunit\TestListener" />
</listeners>
</phpunit>

View File