diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2bfbb9e --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +TEST_APPSIGNAL_KEY= +TEST_LOGOWL_KEY= +TEST_RAYGUN_KEY= +TEST_SENTRY_DSN= \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 6682db7..d7e8f94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,5 @@ notifications: before_script: composer install --ignore-platform-reqs script: -- vendor/bin/phpunit --configuration phpunit.xml -- vendor/bin/psalm --show-info=true +- composer check +- composer test diff --git a/README.md b/README.md index ba39902..410781e 100644 --- a/README.md +++ b/README.md @@ -90,10 +90,20 @@ Below is a list of supported adapters, and thier compatibly tested versions alon ## Tests -To run all unit tests, use the following Docker command: +If you need to install dependencies, run: ```bash -docker run --rm -e TEST_RAYGUN_KEY=KKKK -e TEST_APPSIGNAL_KEY=XXXX -e TEST_SENTRY_DSN=XXXX -v $(pwd):$(pwd):rw -w $(pwd) php:8.0-cli-alpine sh -c "vendor/bin/phpunit --configuration phpunit.xml tests" +docker run --rm --interactive --tty \ + --volume $PWD:/app \ + composer update --ignore-platform-reqs --optimize-autoloader --no-plugins --no-scripts --prefer-dist +``` + +To run all unit tests, prepare the `.env` file using `.env.example` as a template. Then, run: + +```bash +docker run --rm --interactive --tty --env-file .env \ + --volume $PWD:/app \ + composer test ``` > Make sure to replace `TEST_SENTRY_DSN` with actual keys from Sentry. @@ -105,7 +115,9 @@ docker run --rm -e TEST_RAYGUN_KEY=KKKK -e TEST_APPSIGNAL_KEY=XXXX -e TEST_SENTR To run static code analysis, use the following Psalm command: ```bash -docker run --rm -v $(pwd):$(pwd):rw -w $(pwd) php:8.0-cli-alpine sh -c "vendor/bin/psalm --show-info=true" +docker run --rm --interactive --tty \ + --volume $PWD:/app \ + composer check ``` ## System Requirements diff --git a/composer.json b/composer.json index f1354fd..6614a56 100644 --- a/composer.json +++ b/composer.json @@ -7,11 +7,23 @@ "scripts": { "check": "./vendor/bin/phpstan analyse --level max src tests", "lint": "./vendor/bin/pint --test", - "format": "./vendor/bin/pint" + "format": "./vendor/bin/pint", + "test-unit": "./vendor/bin/phpunit --configuration phpunit.xml --testsuite unit --debug", + "test-e2e": "./vendor/bin/phpunit --configuration phpunit.xml --testsuite e2e --debug", + "test": [ + "@test-unit", + "@test-e2e" + ] }, "autoload": { "psr-4": {"Utopia\\Logger\\": "src/Logger"} }, + "autoload-dev": { + "psr-4": { + "Utopia\\Tests\\E2E\\":"tests/e2e", + "Utopia\\Tests\\Unit\\":"tests/unit" + } + }, "require": { "php": ">=8.0" }, diff --git a/docs/tutorials/add-logger-adapter.md b/docs/tutorials/add-logger-adapter.md index d37c24b..a3aeef9 100644 --- a/docs/tutorials/add-logger-adapter.md +++ b/docs/tutorials/add-logger-adapter.md @@ -139,16 +139,35 @@ In `src/Logger/Logger.php` update variable `const PROVIDERS` to include your pro ## 3. Test your adapter -After you finished adding your new adapter, you should write a proper test for it. To do that, you enter `tests/LoggerTests.php` and take a look at `testAdapters()` method. In there, we already build a whole log object and all you need to do is to push the log using your provider. Take a look at how test for already existign adapter looks or use template below: +After you finished adding your new adapter, you should write a proper test for it. To do that, you enter `tests/e2e/Adapter` and create a new `XXXTest.php` wtih something like: ```php -// Test [ADAPTER_NAME] -$adapter = new [ADAPTER_NAME](); // TODO: Use `getenv()` method to provide private keys as env variables -$logger = new Logger($adapter); -$response = $logger->addLog($log); -// TODO: Expect success response either by checking body or response status code using `assertEquals()` +adapter = new [XXX]( + $param1 ? $param1 : '', + $param2 ? $param2 : '', + ); + $this->expected = 202; // Expected status code for successful log creation + } +} + ``` +Take a look at any other adapter as an example. + ## 4. Raise a pull request First of all, commit the changes with the message `Added XXX Adapter` (where `XXX` is adapter name) and push it. This will publish a new branch to your forked version of utopia/logger. If you visit it at `github.com/YOUR_USERNAME/logger`, you will see a new alert saying you are ready to submit a pull request. Follow the steps GitHub provides, and at the end, you will have your pull request submitted. diff --git a/phpunit.xml b/phpunit.xml index ec39a7e..d83e915 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,8 +9,11 @@ stopOnFailure="false" > - - ./tests/ + + ./tests/unit + + + ./tests/e2e \ No newline at end of file diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php deleted file mode 100755 index f7f5f5d..0000000 --- a/tests/LoggerTest.php +++ /dev/null @@ -1,185 +0,0 @@ -getEmail()); - self::assertEquals(null, $user->getUsername()); - self::assertEquals(null, $user->getId()); - - $user = new User('618e291cd8949'); - self::assertEquals('618e291cd8949', $user->getId()); - - $user = new User(null, 'matej@appwrite.io'); - self::assertEquals('matej@appwrite.io', $user->getEmail()); - - $user = new User(null, null, 'Meldiron'); - self::assertEquals('Meldiron', $user->getUsername()); - } - - /** - * @throws Exception - */ - public function testLog(): void - { - $log = new Log(); - - $timestamp = \microtime(true); - $log->setTimestamp($timestamp); - self::assertEquals($timestamp, $log->getTimestamp()); - - $log->setType(Log::TYPE_ERROR); - self::assertEquals(Log::TYPE_ERROR, $log->getType()); - $log->setType(Log::TYPE_DEBUG); - self::assertEquals(Log::TYPE_DEBUG, $log->getType()); - $log->setType(Log::TYPE_WARNING); - self::assertEquals(Log::TYPE_WARNING, $log->getType()); - $log->setType(Log::TYPE_VERBOSE); - self::assertEquals(Log::TYPE_VERBOSE, $log->getType()); - $log->setType(Log::TYPE_INFO); - self::assertEquals(Log::TYPE_INFO, $log->getType()); - - $log->setMessage("Cannot read 'user' of undefined"); - self::assertEquals("Cannot read 'user' of undefined", $log->getMessage()); - - $log->setVersion('0.11.0'); - self::assertEquals('0.11.0', $log->getVersion()); - - $log->setEnvironment(Log::ENVIRONMENT_PRODUCTION); - self::assertEquals(Log::ENVIRONMENT_PRODUCTION, $log->getEnvironment()); - $log->setEnvironment(Log::ENVIRONMENT_STAGING); - self::assertEquals(Log::ENVIRONMENT_STAGING, $log->getEnvironment()); - - $log->setNamespace('getAuthUser'); - self::assertEquals('getAuthUser', $log->getNamespace()); - - $log->setAction('authGuard'); - self::assertEquals('authGuard', $log->getAction()); - - $log->setServer('aws-001'); - self::assertEquals('aws-001', $log->getServer()); - - $log->addExtra('isLoggedIn', false); - self::assertEquals(['isLoggedIn' => false], $log->getExtra()); - - $log->addTag('authMethod', 'session'); - $log->addTag('authProvider', 'basic'); - self::assertEquals(['authMethod' => 'session', 'authProvider' => 'basic'], $log->getTags()); - - $userId = 'myid123'; - $user = new User($userId); - $log->setUser($user); - self::assertEquals($user, $log->getUser()); - self::assertEquals($userId, $log->getUser()?->getId()); - - $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG, 'http', 'DELETE /api/v1/database/abcd1234/efgh5678', $timestamp); - $log->addBreadcrumb($breadcrumb); - self::assertEquals([$breadcrumb], $log->getBreadcrumbs()); - self::assertEquals(Log::TYPE_DEBUG, $log->getBreadcrumbs()[0]->getType()); - self::assertEquals('http', $log->getBreadcrumbs()[0]->getCategory()); - self::assertEquals('DELETE /api/v1/database/abcd1234/efgh5678', $log->getBreadcrumbs()[0]->getMessage()); - self::assertEquals($timestamp, $log->getBreadcrumbs()[0]->getTimestamp()); - } - - /** - * @throws Exception - */ - public function testLogBreadcrumb(): void - { - $timestamp = \microtime(true); - $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG, 'http', 'POST /user', $timestamp); - - self::assertEquals(Log::TYPE_DEBUG, $breadcrumb->getType()); - self::assertEquals('http', $breadcrumb->getCategory()); - self::assertEquals('POST /user', $breadcrumb->getMessage()); - self::assertEquals($timestamp, $breadcrumb->getTimestamp()); - - $breadcrumb = new Breadcrumb(Log::TYPE_INFO, 'http', 'POST /user', $timestamp); - self::assertEquals(Log::TYPE_INFO, $breadcrumb->getType()); - $breadcrumb = new Breadcrumb(Log::TYPE_VERBOSE, 'http', 'POST /user', $timestamp); - self::assertEquals(Log::TYPE_VERBOSE, $breadcrumb->getType()); - $breadcrumb = new Breadcrumb(Log::TYPE_ERROR, 'http', 'POST /user', $timestamp); - self::assertEquals(Log::TYPE_ERROR, $breadcrumb->getType()); - $breadcrumb = new Breadcrumb(Log::TYPE_WARNING, 'http', 'POST /user', $timestamp); - self::assertEquals(Log::TYPE_WARNING, $breadcrumb->getType()); - - // Assert FAILS - self::expectException(ArgumentCountError::class); - $breadcrumb = new Breadcrumb(); // @phpstan-ignore-line - $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG); // @phpstan-ignore-line - $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG, 'http'); // @phpstan-ignore-line - $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG, 'http', 'POST /user'); // @phpstan-ignore-line - } - - /** - * @throws Exception - */ - public function testAdapters(): void - { - // Prepare log - $log = new Log(); - $log->setAction('controller.database.deleteDocument'); - $log->setEnvironment('production'); - $log->setNamespace('api'); - $log->setServer('digitalocean-us-001'); - $log->setType(Log::TYPE_ERROR); - $log->setVersion('0.11.5'); - $log->setMessage('Document efgh5678 not found'); - $log->setUser(new User('efgh5678')); - $log->addBreadcrumb(new Breadcrumb(Log::TYPE_DEBUG, 'http', 'DELETE /api/v1/database/abcd1234/efgh5678', \microtime(true) - 500)); - $log->addBreadcrumb(new Breadcrumb(Log::TYPE_DEBUG, 'auth', 'Using API key', \microtime(true) - 400)); - $log->addBreadcrumb(new Breadcrumb(Log::TYPE_INFO, 'auth', 'Authenticated with * Using API Key', \microtime(true) - 350)); - $log->addBreadcrumb(new Breadcrumb(Log::TYPE_INFO, 'database', 'Found collection abcd1234', \microtime(true) - 300)); - $log->addBreadcrumb(new Breadcrumb(Log::TYPE_DEBUG, 'database', 'Permission for collection abcd1234 met', \microtime(true) - 200)); - $log->addBreadcrumb(new Breadcrumb(Log::TYPE_ERROR, 'database', 'Missing document when searching by ID!', \microtime(true))); - $log->addTag('sdk', 'Flutter'); - $log->addTag('sdkVersion', '0.0.1'); - $log->addTag('authMode', 'default'); - $log->addTag('authMethod', 'cookie'); - $log->addTag('authProvider', 'MagicLink'); - $log->addExtra('urgent', false); - $log->addExtra('isExpected', true); - $log->addExtra('file', '/User/example/server/src/server/server.js'); - $log->addExtra('line', '15'); - - // Test Sentry - $adapter = new Sentry(\getenv('TEST_SENTRY_DSN') ?: ''); - $logger = new Logger($adapter); - $response = $logger->addLog($log); - $this->assertEquals(200, $response); - - // Test AppSignal - $appSignalKey = \getenv('TEST_APPSIGNAL_KEY'); - $adapter = new AppSignal($appSignalKey ? $appSignalKey : ''); - $logger = new Logger($adapter); - $response = $logger->addLog($log); - $this->assertEquals(200, $response); - - // Test Raygun - $raygunKey = \getenv('TEST_RAYGUN_KEY'); - $adapter = new Raygun($raygunKey ? $raygunKey : ''); - $logger = new Logger($adapter); - $response = $logger->addLog($log); - $this->assertEquals(202, $response); - - // Test LogOwl - $logOwlKey = \getenv('TEST_LOGOWL_KEY'); - $adapter = new LogOwl($logOwlKey ? $logOwlKey : ''); - $logger = new Logger($adapter); - $response = $logger->addLog($log); - $this->assertEquals(200, $response); - } -} diff --git a/tests/e2e/Adapter/AppSignalTest.php b/tests/e2e/Adapter/AppSignalTest.php new file mode 100644 index 0000000..6e04df8 --- /dev/null +++ b/tests/e2e/Adapter/AppSignalTest.php @@ -0,0 +1,16 @@ +adapter = new AppSignal($appSignalKey ? $appSignalKey : ''); + } +} diff --git a/tests/e2e/Adapter/LogOwlTest.php b/tests/e2e/Adapter/LogOwlTest.php new file mode 100644 index 0000000..541b475 --- /dev/null +++ b/tests/e2e/Adapter/LogOwlTest.php @@ -0,0 +1,16 @@ +adapter = new LogOwl($logOwlKey ? $logOwlKey : ''); + } +} diff --git a/tests/e2e/Adapter/RaygunTest.php b/tests/e2e/Adapter/RaygunTest.php new file mode 100644 index 0000000..ddcc770 --- /dev/null +++ b/tests/e2e/Adapter/RaygunTest.php @@ -0,0 +1,17 @@ +adapter = new Raygun($raygunKey ? $raygunKey : ''); + $this->expected = 202; + } +} diff --git a/tests/e2e/Adapter/SentryTest.php b/tests/e2e/Adapter/SentryTest.php new file mode 100644 index 0000000..4acfd1e --- /dev/null +++ b/tests/e2e/Adapter/SentryTest.php @@ -0,0 +1,15 @@ +adapter = new Sentry(\getenv('TEST_SENTRY_DSN') ?: ''); + } +} diff --git a/tests/e2e/AdapterBase.php b/tests/e2e/AdapterBase.php new file mode 100755 index 0000000..a06d184 --- /dev/null +++ b/tests/e2e/AdapterBase.php @@ -0,0 +1,61 @@ +log = new Log(); + $this->log->setAction('controller.database.deleteDocument'); + $this->log->setEnvironment('production'); + $this->log->setNamespace('api'); + $this->log->setServer('digitalocean-us-001'); + $this->log->setType(Log::TYPE_ERROR); + $this->log->setVersion('0.11.5'); + $this->log->setMessage('Document efgh5678 not found'); + $this->log->setUser(new User('efgh5678')); + $this->log->addBreadcrumb(new Breadcrumb(Log::TYPE_DEBUG, 'http', 'DELETE /api/v1/database/abcd1234/efgh5678', \microtime(true) - 500)); + $this->log->addBreadcrumb(new Breadcrumb(Log::TYPE_DEBUG, 'auth', 'Using API key', \microtime(true) - 400)); + $this->log->addBreadcrumb(new Breadcrumb(Log::TYPE_INFO, 'auth', 'Authenticated with * Using API Key', \microtime(true) - 350)); + $this->log->addBreadcrumb(new Breadcrumb(Log::TYPE_INFO, 'database', 'Found collection abcd1234', \microtime(true) - 300)); + $this->log->addBreadcrumb(new Breadcrumb(Log::TYPE_DEBUG, 'database', 'Permission for collection abcd1234 met', \microtime(true) - 200)); + $this->log->addBreadcrumb(new Breadcrumb(Log::TYPE_ERROR, 'database', 'Missing document when searching by ID!', \microtime(true))); + $this->log->addTag('sdk', 'Flutter'); + $this->log->addTag('sdkVersion', '0.0.1'); + $this->log->addTag('authMode', 'default'); + $this->log->addTag('authMethod', 'cookie'); + $this->log->addTag('authProvider', 'MagicLink'); + $this->log->addExtra('urgent', false); + $this->log->addExtra('isExpected', true); + $this->log->addExtra('file', '/User/example/server/src/server/server.js'); + $this->log->addExtra('line', '15'); + } + + /** + * @throws \Throwable + */ + public function testAdapter(): void + { + if (empty($this->log) || empty($this->adapter)) { + throw new \Exception('Log or adapter not set'); + } + $logger = new Logger($this->adapter); + $response = $logger->addLog($this->log); + $this->assertEquals($this->expected, $response); + } +} diff --git a/tests/unit/Log/BreadcrumbTest.php b/tests/unit/Log/BreadcrumbTest.php new file mode 100644 index 0000000..ec1c8db --- /dev/null +++ b/tests/unit/Log/BreadcrumbTest.php @@ -0,0 +1,40 @@ +getType()); + self::assertEquals('http', $breadcrumb->getCategory()); + self::assertEquals('POST /user', $breadcrumb->getMessage()); + self::assertEquals($timestamp, $breadcrumb->getTimestamp()); + + $breadcrumb = new Breadcrumb(Log::TYPE_INFO, 'http', 'POST /user', $timestamp); + self::assertEquals(Log::TYPE_INFO, $breadcrumb->getType()); + $breadcrumb = new Breadcrumb(Log::TYPE_VERBOSE, 'http', 'POST /user', $timestamp); + self::assertEquals(Log::TYPE_VERBOSE, $breadcrumb->getType()); + $breadcrumb = new Breadcrumb(Log::TYPE_ERROR, 'http', 'POST /user', $timestamp); + self::assertEquals(Log::TYPE_ERROR, $breadcrumb->getType()); + $breadcrumb = new Breadcrumb(Log::TYPE_WARNING, 'http', 'POST /user', $timestamp); + self::assertEquals(Log::TYPE_WARNING, $breadcrumb->getType()); + + // Assert FAILS + self::expectException(\ArgumentCountError::class); + $breadcrumb = new Breadcrumb(); // @phpstan-ignore-line + $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG); // @phpstan-ignore-line + $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG, 'http'); // @phpstan-ignore-line + $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG, 'http', 'POST /user'); // @phpstan-ignore-line + } +} diff --git a/tests/unit/Log/UserTest.php b/tests/unit/Log/UserTest.php new file mode 100644 index 0000000..c2d296f --- /dev/null +++ b/tests/unit/Log/UserTest.php @@ -0,0 +1,27 @@ +getEmail()); + self::assertEquals(null, $user->getUsername()); + self::assertEquals(null, $user->getId()); + + $user = new User('618e291cd8949'); + self::assertEquals('618e291cd8949', $user->getId()); + + $user = new User(null, 'matej@appwrite.io'); + self::assertEquals('matej@appwrite.io', $user->getEmail()); + + $user = new User(null, null, 'Meldiron'); + self::assertEquals('Meldiron', $user->getUsername()); + } +} diff --git a/tests/unit/LogTest.php b/tests/unit/LogTest.php new file mode 100644 index 0000000..82b00d2 --- /dev/null +++ b/tests/unit/LogTest.php @@ -0,0 +1,75 @@ +setTimestamp($timestamp); + self::assertEquals($timestamp, $log->getTimestamp()); + + $log->setType(Log::TYPE_ERROR); + self::assertEquals(Log::TYPE_ERROR, $log->getType()); + $log->setType(Log::TYPE_DEBUG); + self::assertEquals(Log::TYPE_DEBUG, $log->getType()); + $log->setType(Log::TYPE_WARNING); + self::assertEquals(Log::TYPE_WARNING, $log->getType()); + $log->setType(Log::TYPE_VERBOSE); + self::assertEquals(Log::TYPE_VERBOSE, $log->getType()); + $log->setType(Log::TYPE_INFO); + self::assertEquals(Log::TYPE_INFO, $log->getType()); + + $log->setMessage("Cannot read 'user' of undefined"); + self::assertEquals("Cannot read 'user' of undefined", $log->getMessage()); + + $log->setVersion('0.11.0'); + self::assertEquals('0.11.0', $log->getVersion()); + + $log->setEnvironment(Log::ENVIRONMENT_PRODUCTION); + self::assertEquals(Log::ENVIRONMENT_PRODUCTION, $log->getEnvironment()); + $log->setEnvironment(Log::ENVIRONMENT_STAGING); + self::assertEquals(Log::ENVIRONMENT_STAGING, $log->getEnvironment()); + + $log->setNamespace('getAuthUser'); + self::assertEquals('getAuthUser', $log->getNamespace()); + + $log->setAction('authGuard'); + self::assertEquals('authGuard', $log->getAction()); + + $log->setServer('aws-001'); + self::assertEquals('aws-001', $log->getServer()); + + $log->addExtra('isLoggedIn', false); + self::assertEquals(['isLoggedIn' => false], $log->getExtra()); + + $log->addTag('authMethod', 'session'); + $log->addTag('authProvider', 'basic'); + self::assertEquals(['authMethod' => 'session', 'authProvider' => 'basic'], $log->getTags()); + + $userId = 'myid123'; + $user = new User($userId); + $log->setUser($user); + self::assertEquals($user, $log->getUser()); + self::assertEquals($userId, $log->getUser()?->getId()); + + $breadcrumb = new Breadcrumb(Log::TYPE_DEBUG, 'http', 'DELETE /api/v1/database/abcd1234/efgh5678', $timestamp); + $log->addBreadcrumb($breadcrumb); + self::assertEquals([$breadcrumb], $log->getBreadcrumbs()); + self::assertEquals(Log::TYPE_DEBUG, $log->getBreadcrumbs()[0]->getType()); + self::assertEquals('http', $log->getBreadcrumbs()[0]->getCategory()); + self::assertEquals('DELETE /api/v1/database/abcd1234/efgh5678', $log->getBreadcrumbs()[0]->getMessage()); + self::assertEquals($timestamp, $log->getBreadcrumbs()[0]->getTimestamp()); + } +}