diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 68b0468..00f251b 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -6,8 +6,8 @@ jobs: run: uses: flarum/framework/.github/workflows/REUSABLE_backend.yml@1.x with: - enable_backend_testing: false + enable_backend_testing: true enable_phpstan: true - php_versions: '["8.0", "8.1", "8.2", "8.3"]' + php_versions: '["8.0", "8.1", "8.2", "8.3", "8.4"]' backend_directory: . diff --git a/.gitignore b/.gitignore index d34e123..528d586 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules vendor composer.lock js/dist +.phpunit.result.cache diff --git a/composer.json b/composer.json index 9fabd1c..3ed7220 100644 --- a/composer.json +++ b/composer.json @@ -58,20 +58,38 @@ }, "flarum-cli": { "modules": { - "githubActions": true + "githubActions": true, + "backendTesting": true } } }, "require-dev": { "flarum/phpstan": "*", "fof/drafts": "*", - "fof/default-user-preferences": "*" + "fof/default-user-preferences": "*", + "flarum/testing": "^1.0.0" }, "scripts": { "analyse:phpstan": "phpstan analyse", - "clear-cache:phpstan": "phpstan clear-result-cache" + "clear-cache:phpstan": "phpstan clear-result-cache", + "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": { - "analyse:phpstan": "Run static analysis" + "analyse:phpstan": "Run static analysis", + "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." + }, + "autoload-dev": { + "psr-4": { + "FoF\\GeoIP\\Tests\\": "tests/" + } } } diff --git a/src/Listeners/RetrieveIP.php b/src/Listeners/RetrieveIP.php index f864add..7223292 100644 --- a/src/Listeners/RetrieveIP.php +++ b/src/Listeners/RetrieveIP.php @@ -40,9 +40,9 @@ public function subscribe(Dispatcher $events) $events->listen(PostSaving::class, [$this, 'handlePost']); } - public function retrieveIP(string $ip): void + public function retrieveIP(?string $ip): void { - if ($this->geo->isValidIP($ip) && !$this->geo->recordExistsForIP($ip)) { + if ($ip !== null && $this->geo->isValidIP($ip) && !$this->geo->recordExistsForIP($ip)) { $this->queue->push(new Jobs\RetrieveIP($ip)); } } diff --git a/tests/fixtures/.gitkeep b/tests/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/Api/NullIPTest.php b/tests/integration/Api/NullIPTest.php new file mode 100644 index 0000000..23a62f5 --- /dev/null +++ b/tests/integration/Api/NullIPTest.php @@ -0,0 +1,114 @@ +extension('fof-geoip'); + + $this->prepareDatabase([ + 'users' => [ + $this->normalUser(), + ], + 'discussions' => [ + ['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1], + ], + 'posts' => [ + ['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '

foo bar

', 'ip_address' => null], + ], + ]); + } + + public function userTypes(): array + { + return [ + [null], + [1], + [2], + ]; + } + + /** + * @test + * + * @dataProvider userTypes + */ + public function can_show_discussion_with_null_ip(?int $userId) + { + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => $userId, + ]) + ); + + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($response->getBody(), true); + + $included = $data['included']; + + $posts = array_values(array_filter($included, function ($item) { + return $item['type'] === 'posts'; + })); + + $firstPost = $posts[0]; + + $this->assertEquals('posts', $firstPost['type']); + $this->assertEquals('

foo bar

', $firstPost['attributes']['contentHtml']); + + // In this test scenario, only user Id 1 (admin) should be able to see the IP address + if ($userId === 1) { + $this->assertArrayHasKey('ipAddress', $firstPost['attributes'], 'IP address should be visible'); + $this->assertNull($firstPost['attributes']['ipAddress']); + } else { + $this->assertArrayNotHasKey('ipAddress', $firstPost['attributes'], 'IP address should not be visible'); + } + } + + /** + * @test + */ + public function can_edit_post_with_null_ip() + { + $response = $this->send( + $this->request('PATCH', '/api/posts/1', [ + 'authenticatedAs' => 1, + 'json' => [ + 'data' => [ + 'type' => 'posts', + 'id' => '1', + 'attributes' => [ + 'content' => 'foo bar - edited', + ], + ], + ], + ]) + ); + + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($response->getBody(), true); + + $this->assertEquals('posts', $data['data']['type']); + $this->assertEquals('foo bar - edited', $data['data']['attributes']['contentHtml']); + $this->assertArrayHasKey('ipAddress', $data['data']['attributes']); + $this->assertNull($data['data']['attributes']['ipAddress']); + } +} diff --git a/tests/integration/setup.php b/tests/integration/setup.php new file mode 100644 index 0000000..3200b11 --- /dev/null +++ b/tests/integration/setup.php @@ -0,0 +1,18 @@ +run(); diff --git a/tests/phpunit.integration.xml b/tests/phpunit.integration.xml new file mode 100644 index 0000000..90fbbff --- /dev/null +++ b/tests/phpunit.integration.xml @@ -0,0 +1,25 @@ + + + + + ../src/ + + + + + ./integration + ./integration/tmp + + + diff --git a/tests/phpunit.unit.xml b/tests/phpunit.unit.xml new file mode 100644 index 0000000..d3a4a3e --- /dev/null +++ b/tests/phpunit.unit.xml @@ -0,0 +1,27 @@ + + + + + ../src/ + + + + + ./unit + + + + + + diff --git a/tests/unit/.gitkeep b/tests/unit/.gitkeep new file mode 100644 index 0000000..e69de29