Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ jobs:
strategy:
matrix:
container: [
"kirschbaumdevelopment/laravel-test-runner:8.2",
"kirschbaumdevelopment/laravel-test-runner:8.3"
"kirschbaumdevelopment/laravel-test-runner:8.3",
"kirschbaumdevelopment/laravel-test-runner:8.4",
"kirschbaumdevelopment/laravel-test-runner:8.5"
]

container:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ resources/views/vendor/
composer.lock
phpunit.xml

/.phpunit.cache
2 changes: 1 addition & 1 deletion .php-cs-fixer.cache

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Sendportal

## 4.0.0 - Unreleased
- Update to Laravel 13 and sendportal-core 4
- Require PHP 8.3+
- Rename `VerifyCsrfToken` middleware to `PreventRequestForgery`
- Upgrade to PHPUnit 12 and convert test annotations to attributes

## 3.0.0 - 2024-04-19
- Update to Laravel 11 ([#290](https://github.com/mettle/sendportal/pull/290)

Expand Down
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ The [SendPortal](https://github.com/mettle/sendportal) application acts as a wra

## Installation

If you would like to install SendPortal as a stand-alone application, please follow the [installation guide](https://sendportal.io/docs/v2/getting-started/installation).
If you would like to install SendPortal as a stand-alone application, please follow the [installation guide](https://sendportal.io/docs/v4/getting-started/installation).

If you would like to add SendPortal to an existing application, please follow the [package installation guide](https://sendportal.io/docs/v2/getting-started/package-installation).
If you would like to add SendPortal to an existing application, please follow the [package installation guide](https://sendportal.io/docs/v4/getting-started/package-installation).

If you are upgrading from SendPortal V3, please follow the [upgrade guide](https://sendportal.io/docs/v4/getting-started/upgrade-guide).

## Requirements
SendPortal V3 requires:
SendPortal V4 requires:

- PHP 8.3+
- Laravel 13
- MySQL (≥ 5.7) or PostgreSQL (≥ 10)

- PHP 8.2+
- Laravel 10+
- MySQL (≥ 5.7) or PostgreSQL (≥ 9.4)
If you are on PHP 8.2 or Laravel 10/11, please use [SendPortal V3](https://github.com/mettle/sendportal/releases/tag/v3.0.0)

If you are on an earlier version of PHP (7.3+) or Laravel (8+), please use [SendPortal V2](https://github.com/mettle/sendportal/releases/tag/v2.0.4)
2 changes: 1 addition & 1 deletion app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Kernel extends HttpKernel
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\App\Http\Middleware\PreventRequestForgery::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as Middleware;

class VerifyCsrfToken extends Middleware
class PreventRequestForgery extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
Expand Down
26 changes: 13 additions & 13 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
"description": "SendPortal. Open-source self-hosted email marketing. Manage your own newsletters at a fraction of the cost.",
"license": "MIT",
"require": {
"php": "^8.2|^8.3",
"guzzlehttp/guzzle": "^7.8.1",
"laravel/framework": "^11.0",
"laravel/horizon": "^5.24",
"laravel/tinker": "^2.9",
"livewire/livewire": "^3.4",
"laravel/ui": "^4.5",
"mettle/sendportal-core": "^3.0"
"php": "^8.3",
"guzzlehttp/guzzle": "^7.10",
"laravel/framework": "^13.0",
"laravel/horizon": "^5.50",
"laravel/tinker": "^3.0",
"livewire/livewire": "^3.8",
"laravel/ui": "^4.6",
"mettle/sendportal-core": "^4.0"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.1",
"phpunit/phpunit": "^10.5",
"fakerphp/faker": "^1.24",
"mockery/mockery": "^1.6.12",
"nunomaduro/collision": "^8.9",
"phpunit/phpunit": "^12.5",
"roave/security-advisories": "dev-master",
"spatie/laravel-ignition": "^2.5.1"
"spatie/laravel-ignition": "^2.12"
},
"config": {
"optimize-autoloader": true,
Expand Down
2 changes: 1 addition & 1 deletion config/database.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],

Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<testsuites>
<testsuite name="Test Suite">
<directory>tests</directory>
Expand Down
5 changes: 3 additions & 2 deletions tests/Feature/Auth/AuthConfigDisabledTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthConfigDisabledTest extends TestCase
Expand All @@ -21,7 +22,7 @@ public function setUp(): void
parent::setUp();
}

/** @test */
#[Test]
public function the_registration_routes_result_in_404()
{
$this->get('/register')->assertNotFound();
Expand All @@ -32,7 +33,7 @@ public function the_registration_routes_result_in_404()
$this->post('email/resend')->assertNotFound();
}

/** @test */
#[Test]
public function the_password_reset_routes_result_in_404()
{
$this->get('password/reset')->assertNotFound();
Expand Down
5 changes: 3 additions & 2 deletions tests/Feature/Auth/AuthConfigEnabledTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class AuthConfigEnabledTest extends TestCase
Expand All @@ -21,13 +22,13 @@ public function setUp(): void
parent::setUp();
}

/** @test */
#[Test]
public function the_registration_routes_result_in_200()
{
$this->get('/register')->assertOk();
}

/** @test */
#[Test]
public function the_password_reset_routes_result_in_200()
{
$this->get('password/reset')->assertOk();
Expand Down
13 changes: 6 additions & 7 deletions tests/Feature/Auth/WorkspaceApiTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Str;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class WorkspaceApiTokenTest extends TestCase
{
use RefreshDatabase;
use WithFaker;

/**
* @test
*
* @group workspace_user_test
*/
#[Test]
#[Group('workspace_user_test')]
public function valid_api_token_grants_access()
{
[$workspace, $user] = $this->createUserAndWorkspace();
Expand All @@ -37,7 +36,7 @@ public function valid_api_token_grants_access()
$response->assertStatus(200);
}

/** @test */
#[Test]
public function a_401_is_thrown_when_invalid_token_provided()
{
[$workspace, $user] = $this->createUserAndWorkspace();
Expand All @@ -55,7 +54,7 @@ public function a_401_is_thrown_when_invalid_token_provided()
$response->assertUnauthorized();
}

/** @test */
#[Test]
public function a_401_is_thrown_when_no_token_is_provided()
{
$response = $this->get(route('sendportal.api.subscribers.index'));
Expand Down
11 changes: 6 additions & 5 deletions tests/Feature/Invitations/ExistingUserInvitationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
use App\Models\Workspace;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
use PHPUnit\Framework\Attributes\Test;
use Ramsey\Uuid\Uuid;
use Tests\TestCase;

class ExistingUserInvitationTest extends TestCase
{
use RefreshDatabase;

/** @test */
#[Test]
public function a_user_can_see_their_invitations()
{
// given
Expand All @@ -42,7 +43,7 @@ public function a_user_can_see_their_invitations()
$response->assertSee('Reject');
}

/** @test */
#[Test]
public function a_user_cannot_see_another_users_invitations()
{
// given
Expand Down Expand Up @@ -70,7 +71,7 @@ public function a_user_cannot_see_another_users_invitations()
$response->assertDontSee($newWorkspace->name);
}

/** @test */
#[Test]
public function a_user_can_accept_valid_invitations()
{
// given
Expand All @@ -97,7 +98,7 @@ public function a_user_can_accept_valid_invitations()
self::assertTrue($user->fresh()->onWorkspace($newWorkspace));
}

/** @test */
#[Test]
public function a_user_can_reject_invitations()
{
// given
Expand Down Expand Up @@ -128,7 +129,7 @@ public function a_user_can_reject_invitations()
]);
}

/** @test */
#[Test]
public function a_user_cannot_accept_an_expired_invitation()
{
// given
Expand Down
7 changes: 4 additions & 3 deletions tests/Feature/Invitations/NewUserInvitationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Event;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class NewUserInvitationTest extends TestCase
Expand All @@ -24,7 +25,7 @@ public function setUp(): void
parent::setUp();
}

/** @test */
#[Test]
public function a_new_user_can_register_with_an_invitation_to_an_existing_workspace()
{
Event::fake();
Expand Down Expand Up @@ -67,7 +68,7 @@ public function a_new_user_can_register_with_an_invitation_to_an_existing_worksp
);
}

/** @test */
#[Test]
public function a_user_cannot_see_the_register_form_with_an_invalid_invitation()
{
// when
Expand All @@ -78,7 +79,7 @@ public function a_user_cannot_see_the_register_form_with_an_invalid_invitation()
$response->assertSessionHas('error', 'The invitation is no longer valid.');
}

/** @test */
#[Test]
public function registrations_fail_validation_when_invitation_is_invalid()
{
// given
Expand Down
5 changes: 3 additions & 2 deletions tests/Feature/Setup/SetupControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class SetupControllerTest extends TestCase
{
use RefreshDatabase;

/** @test */
#[Test]
public function it_should_show_the_setup_view_if_sendportal_is_not_installed()
{
$this
Expand All @@ -21,7 +22,7 @@ public function it_should_show_the_setup_view_if_sendportal_is_not_installed()
->assertSeeLivewire('setup');
}

/** @test */
#[Test]
public function it_should_redirect_the_user_to_the_login_page_if_sendportal_is_already_installed()
{
User::factory()->create();
Expand Down
20 changes: 15 additions & 5 deletions tests/Feature/Setup/SetupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,32 @@
use App\Livewire\Setup;
use App\Setup\Admin;
use App\Setup\Env;
use App\Setup\Url;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class SetupTest extends TestCase
{
use RefreshDatabase;

/** @test */
#[Test]
public function the_setup_component_should_render_the_correct_view()
{
Livewire::test(Setup::class)
->assertViewIs('livewire.setup');
}

/** @test */
#[Test]
public function the_setup_component_should_have_6_steps()
{
$setup = Livewire::test(Setup::class);

self::assertCount(6, $setup->get('steps'));
}

/** @test */
#[Test]
public function the_setup_component_should_check_the_first_step_when_mounted_and_stop_if_its_false()
{
$this->mock(
Expand All @@ -46,9 +48,17 @@ function ($mock) {
$setup->assertSet('progress', 0);
}

/** @test */
#[Test]
public function the_setup_command_should_stop_on_the_admin_step_if_there_are_not_users()
{
// These steps depend on the local .env file and APP_URL rather than the code under test.
$this->mock(Env::class, function ($mock) {
$mock->shouldReceive('check')->andReturn(true);
});
$this->mock(Url::class, function ($mock) {
$mock->shouldReceive('check')->andReturn(true);
});

$setup = Livewire::test(Setup::class);

$setup->assertSet('active', 5);
Expand All @@ -57,6 +67,6 @@ public function the_setup_command_should_stop_on_the_admin_step_if_there_are_not
$step = $setup->get('steps')[$setup->get('active')];

self::assertEquals(Admin::class, $step['handler']);
self::assertEquals(5, $step['completed']);
self::assertFalse($step['completed']);
}
}
Loading