diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 9ca453e11c..05090ba0f6 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -5,10 +5,33 @@ on: branches: - main - develop +env: + DB_CONNECTION: mysql + DB_HOST: "127.0.0.1" + DB_DATABASE: firefly + DB_USER: firefly + DB_PASSWORD: secret_firefly_password + jobs: sonarcloud: name: SonarCloud runs-on: ubuntu-latest + services: + mariadb: + image: mariadb:latest + ports: + - 3306:3306 + env: + MYSQL_ROOT_PASSWORD: yes + MYSQL_USER: ${{ env.DB_USER }} + MYSQL_PASSWORD: ${{ env.DB_PASSWORD }} + MYSQL_DATABASE: ${{ env.DB_DATABASE }} + options: >- + --health-cmd="healthcheck.sh --connect --innodb_initialized" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + steps: - uses: actions/checkout@v3 with: @@ -24,16 +47,49 @@ jobs: with: php-version: '8.2' coverage: xdebug + extensions: >- + bcmath + curl + fileinfo + iconv + intl + json + mbstring + openssl + pdo + session + simplexml + sodium + tokenizer + xml + xmlwriter - name: Install Composer dependencies run: composer install --prefer-dist --no-interaction --no-progress --no-scripts + - name: Verify Database connection + env: + PORT: ${{ job.services.mariadb.ports[3306] }} + run: | + while ! mysqladmin ping -h"${{env.DB_HOST}}" -P"${PORT}" --silent; do + sleep 1 + done + - name: Copy environment file - run: cp .env.example .env + run: sed 's@DB_HOST=.*@DB_HOST=${{env.DB_HOST}}@g' .env.example > .env - name: Generate app key run: php artisan key:generate + - name: "Create the database" + run: php artisan firefly-iii:create-database + + - name: "Upgrades the database to the latest version" + run: php artisan firefly-iii:upgrade-database + + - name: "Integrity Database Report" + run: php artisan firefly-iii:report-integrity + - name: "Run tests with coverage" run: composer coverage diff --git a/app/Console/Commands/System/CreateDatabase.php b/app/Console/Commands/System/CreateDatabase.php index b34914e310..5960f59144 100644 --- a/app/Console/Commands/System/CreateDatabase.php +++ b/app/Console/Commands/System/CreateDatabase.php @@ -83,6 +83,7 @@ class CreateDatabase extends Command $pdo = new PDO($dsn, env('DB_USERNAME'), env('DB_PASSWORD'), $options); } catch (PDOException $e) { $this->friendlyError(sprintf('Error when connecting to DB: %s', $e->getMessage())); + return 1; } // only continue when no error. diff --git a/composer.json b/composer.json index f0143e13a5..7819851728 100644 --- a/composer.json +++ b/composer.json @@ -176,7 +176,7 @@ "@php vendor/bin/phpunit -c phpunit.xml --testsuite integration --no-coverage" ], "coverage": [ - "@php vendor/bin/phpunit -c phpunit.xml --testsuite unit" + "@php vendor/bin/phpunit -c phpunit.xml" ] }, "config": { diff --git a/package-lock.json b/package-lock.json index e1b180dcab..32a140e1b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^6.4.0", "@popperjs/core": "^2.11.8", - "alpinejs": "^3.13.1", + "alpinejs": "^3.13.2", "bootstrap": "^5.3.0", "bootstrap5-autocomplete": "^1.1.22", "chart.js": "^4.4.0", @@ -20,8 +20,8 @@ "devDependencies": { "axios": "^1.5.1", "laravel-vite-plugin": "^0.8.1", - "sass": "^1.69.0", - "vite": "^4.4.11", + "sass": "^1.69.4", + "vite": "^4.5.0", "vite-plugin-manifest-sri": "^0.1.0" } }, @@ -425,9 +425,9 @@ "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==" }, "node_modules/alpinejs": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.1.tgz", - "integrity": "sha512-/LZ7mumW02V7AV5xTTftJFHS0I3KOXLl7tHm4xpxXAV+HJ/zjTT0n8MU7RZ6UoGPhmO/i+KEhQojaH/0RsH5tg==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.2.tgz", + "integrity": "sha512-WzojeeN082kLZznGI1HAuP8yFJSWqJ1fGdz2mUjj45H4y0XwToE7fFqtI3mCPRR+BpcSbxT/NL+FyPnYAWSltw==", "dependencies": { "@vue/reactivity": "~3.1.1" } @@ -932,9 +932,9 @@ } }, "node_modules/sass": { - "version": "1.69.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.3.tgz", - "integrity": "sha512-X99+a2iGdXkdWn1akFPs0ZmelUzyAQfvqYc2P/MPTrJRuIRoTffGzT9W9nFqG00S+c8hXzVmgxhUuHFdrwxkhQ==", + "version": "1.69.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.4.tgz", + "integrity": "sha512-+qEreVhqAy8o++aQfCJwp0sklr2xyEzkm9Pp/Igu9wNPoe7EZEQ8X/MBvvXggI2ql607cxKg/RKOwDj6pp2XDA==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -978,9 +978,9 @@ } }, "node_modules/vite": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz", - "integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", + "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", "dev": true, "dependencies": { "esbuild": "^0.18.10", diff --git a/package.json b/package.json index f58614c426..d8b7f71758 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,14 @@ "devDependencies": { "axios": "^1.5.1", "laravel-vite-plugin": "^0.8.1", - "sass": "^1.69.0", - "vite": "^4.4.11", + "sass": "^1.69.4", + "vite": "^4.5.0", "vite-plugin-manifest-sri": "^0.1.0" }, "dependencies": { "@fortawesome/fontawesome-free": "^6.4.0", "@popperjs/core": "^2.11.8", - "alpinejs": "^3.13.1", + "alpinejs": "^3.13.2", "bootstrap": "^5.3.0", "bootstrap5-autocomplete": "^1.1.22", "chart.js": "^4.4.0", diff --git a/phpunit.xml b/phpunit.xml index aa51a02e21..d5008da502 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -33,6 +33,7 @@ stopOnFailure="true"> + diff --git a/tests/integration/Api/Autocomplete/AccountControllerTest.php b/tests/integration/Api/Autocomplete/AccountControllerTest.php index 5eb0ec0ca7..c268f9fb2e 100644 --- a/tests/integration/Api/Autocomplete/AccountControllerTest.php +++ b/tests/integration/Api/Autocomplete/AccountControllerTest.php @@ -23,8 +23,6 @@ declare(strict_types=1); namespace Tests\integration\Api\Autocomplete; -use Laravel\Passport\Passport; -use Log; use Tests\integration\TestCase; /** @@ -32,33 +30,16 @@ use Tests\integration\TestCase; */ class AccountControllerTest extends TestCase { - /** - * - */ - public function setUp(): void - { - parent::setUp(); - Passport::actingAs($this->user()); - Log::info(sprintf('Now in %s.', get_class($this))); - } - /** * @covers \FireflyIII\Api\V1\Controllers\Autocomplete\AccountController + * @runInSeparateProcess */ - public function testAccounts(): void + public function testGivenAnUnauthenticatedRequestWhenCallingTheAccountsEndpointThenReturns401HttpCode(): void { // test API $response = $this->get(route('api.v1.autocomplete.accounts'), ['Accept' => 'application/json']); - $response->assertStatus(200); + $response->assertStatus(401); $response->assertHeader('Content-Type', 'application/json'); - $response->assertSee('Checking Account'); - } - - /** - * - */ - public function testBasic(): void - { - $this->assertTrue(true); + $response->assertContent('{"message":"Unauthenticated","exception":"AuthenticationException"}'); } } diff --git a/tests/integration/Support/NavigationCustomEndOfPeriodTest.php b/tests/integration/Support/NavigationCustomEndOfPeriodTest.php new file mode 100644 index 0000000000..a7bd3c1652 --- /dev/null +++ b/tests/integration/Support/NavigationCustomEndOfPeriodTest.php @@ -0,0 +1,25 @@ +endOfPeriod($from, 'custom'); + $this->assertEquals($expected->toDateString(), $period->toDateString()); + } +} diff --git a/tests/unit/Support/NavigationEndOfPeriodTest.php b/tests/unit/Support/NavigationEndOfPeriodTest.php new file mode 100644 index 0000000000..af86ed7f03 --- /dev/null +++ b/tests/unit/Support/NavigationEndOfPeriodTest.php @@ -0,0 +1,107 @@ + + * + * This file is part of Firefly III (https://github.com/firefly-iii). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +declare(strict_types=1); + +namespace Tests\unit\Support; + +use Carbon\Carbon; +use FireflyIII\Support\Navigation; +use Illuminate\Support\Facades\Log; +use PHPUnit\Framework\TestCase; + +/** + * @group unit-test + * @group support + * @group navigation + */ +class NavigationEndOfPeriodTest extends TestCase +{ + private Navigation $navigation; + + public function __construct(string $name) + { + parent::__construct($name); + $this->navigation = new Navigation(); + } + + public static function provideDates(): array + { + return [ + '1D' => ['frequency' => '1D', 'from' => Carbon::now(), 'expected' => Carbon::now()->endOfDay()], + 'daily' => ['frequency' => 'daily', 'from' => Carbon::now(), 'expected' => Carbon::now()->endOfDay()], + '1W' => ['frequency' => '1W', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeek()->subDay()->endOfDay()], + 'week' => ['frequency' => 'week', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeek()->subDay()->endOfDay()], + 'weekly' => ['frequency' => 'weekly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addWeek()->subDay()->endOfDay()], + 'month' => ['frequency' => 'month', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonth()->subDay()->endOfDay()], + '1M' => ['frequency' => '1M', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonth()->subDay()->endOfDay()], + 'monthly' => ['frequency' => 'monthly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addMonth()->subDay()->endOfDay()], + '3M' => ['frequency' => '3M', 'from' => Carbon::now(), 'expected' => Carbon::now()->addQuarterNoOverflow()->subDay()->endOfDay()], + 'quarter' => ['frequency' => 'quarter', 'from' => Carbon::now(), 'expected' => Carbon::now()->addQuarterNoOverflow()->subDay()->endOfDay()], + 'quarterly' => ['frequency' => 'quarterly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addQuarterNoOverflow()->subDay()->endOfDay()], + 'year' => ['frequency' => 'year', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYearNoOverflow()->subDay()->endOfDay()], + 'yearly' => ['frequency' => 'yearly', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYearNoOverflow()->subDay()->endOfDay()], + '1Y' => ['frequency' => '1Y', 'from' => Carbon::now(), 'expected' => Carbon::now()->addYearNoOverflow()->subDay()->endOfDay()], + 'half-year' => ['frequency' => 'half-year', 'from' => Carbon::parse('2023-05-20'), 'expected' => Carbon::parse('2023-11-19')->endOfDay()], + '6M' => ['frequency' => '6M', 'from' => Carbon::parse('2023-08-20'), 'expected' => Carbon::parse('2024-02-19')], + 'last7' => ['frequency' => 'last7', 'from' => Carbon::now(), 'expected' => Carbon::now()->addDays(7)->endOfDay()], + 'last30' => ['frequency' => 'last30', 'from' => Carbon::now(), 'expected' => Carbon::now()->addDays(30)->endOfDay()], + 'last90' => ['frequency' => 'last90', 'from' => Carbon::now(), 'expected' => Carbon::now()->addDays(90)->endOfDay()], + 'last365' => ['frequency' => 'last365', 'from' => Carbon::now(), 'expected' => Carbon::now()->addDays(365)->endOfDay()], + 'MTD' => ['frequency' => 'MTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->startOfMonth()->startOfDay()], + 'QTD' => ['frequency' => 'QTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->firstOfQuarter()->startOfDay()], + 'YTD' => ['frequency' => 'YTD', 'from' => Carbon::now(), 'expected' => Carbon::now()->startOfYear()->startOfDay()], + 'week 2023-08-05 to 2023-08-11' => ['frequency' => '1W', 'from' => Carbon::parse('2023-08-05'), 'expected' => Carbon::parse('2023-08-11')->endOfDay()], + ]; + } + + /** + * @dataProvider provideDates + */ + public function testGivenADateAndFrequencyWhenCalculateTheDateThenReturnsTheExpectedDateSuccessful(string $frequency, Carbon $from, Carbon $expected) + { + $period = $this->navigation->endOfPeriod($from, $frequency); + $this->assertEquals($expected->toDateString(), $period->toDateString()); + } + + public static function provideUnknownFrequencies(): array + { + return [ + '1day' => ['frequency' => '1day', 'from' => Carbon::now(), 'expected' => Carbon::now()], + 'unknown' => ['frequency' => 'unknown', 'from' => Carbon::now(), 'expected' => Carbon::now()->startOfDay()], + 'empty' => ['frequency' => '', 'from' => Carbon::now(), 'expected' => Carbon::now()->startOfDay()], + ]; + } + + /** + * @dataProvider provideUnknownFrequencies + */ + public function testGivenADateAndUnknownFrequencyWhenCalculateTheDateThenReturnsTheSameDateSuccessful(string $frequency, Carbon $from, Carbon $expected) + { + Log::spy(); + + $period = $this->navigation->endOfPeriod($from, $frequency); + $this->assertEquals($expected->toDateString(), $period->toDateString()); + $expectedMessage = sprintf('Cannot do endOfPeriod for $repeat_freq "%s"', $frequency); + + Log::shouldHaveReceived('error', [$expectedMessage]); + } +}