CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m49s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped
Group classes can now be marked invite-only (us_offerings.access_mode). Invite-only classes are hidden from the public catalog and reachable only when the instructor lets someone in via one of three paths, managed from My Lessons -> My Group Classes: - Add students directly: enrols them now with a pending payment. - Make available: grants registered students access to self-enrol through the normal paid flow (multi-select, emailed a notice). - Invite by email: tokenised registration invite tied to the class for a non-account address; after they register the class becomes enrollable. Reuses an existing pending invite instead of sending a second link. New us_group_access table records grants; GET /offerings merges granted invite-only classes for the caller; enrolment requires a grant (403 invite_required) and flips it to enrolled on success. composer test (487), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 4.8 <[email protected]>
159 lines
4.9 KiB
PHP
159 lines
4.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Unsupervised\Schedular\Tests\Unit\GroupClass;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use Mockery;
|
|
use Unsupervised\Schedular\GroupClass\GroupAccess;
|
|
use Unsupervised\Schedular\GroupClass\GroupAccessRepository;
|
|
use Unsupervised\Schedular\Tests\Unit\TestCase;
|
|
|
|
class GroupAccessRepositoryTest extends TestCase
|
|
{
|
|
private \wpdb $db;
|
|
private GroupAccessRepository $repo;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->db = Mockery::mock(\wpdb::class);
|
|
$this->db->prefix = 'wp_';
|
|
$this->repo = new GroupAccessRepository($this->db);
|
|
}
|
|
|
|
public function testInsertReturnsId(): void
|
|
{
|
|
Functions\expect('current_time')->with('mysql')->andReturn('2026-06-02 09:00:00');
|
|
|
|
$this->db->shouldReceive('insert')
|
|
->once()
|
|
->with(
|
|
'wp_us_group_access',
|
|
Mockery::on(static function (array $d): bool {
|
|
return $d['offering_id'] === 8
|
|
&& $d['student_id'] === 5
|
|
&& $d['status'] === GroupAccess::STATUS_INVITED;
|
|
}),
|
|
['%d', '%d', '%s', '%d', '%s', '%d', '%s']
|
|
);
|
|
$this->db->insert_id = 3;
|
|
|
|
self::assertSame(3, $this->repo->insert(new GroupAccess(offeringId: 8, studentId: 5, invitedBy: 2)));
|
|
}
|
|
|
|
public function testHasGrantTrueWhenCountPositive(): void
|
|
{
|
|
$this->db->shouldReceive('prepare')
|
|
->once()
|
|
->with(
|
|
Mockery::pattern('/offering_id = %d AND student_id = %d AND status IN/'),
|
|
'wp_us_group_access',
|
|
8,
|
|
5,
|
|
GroupAccess::STATUS_INVITED,
|
|
GroupAccess::STATUS_ENROLLED
|
|
)
|
|
->andReturn('SELECT ...');
|
|
$this->db->shouldReceive('get_var')->andReturn('1');
|
|
|
|
self::assertTrue($this->repo->hasGrant(8, 5));
|
|
}
|
|
|
|
public function testHasGrantFalseWhenZero(): void
|
|
{
|
|
$this->db->shouldReceive('prepare')->andReturn('SELECT ...');
|
|
$this->db->shouldReceive('get_var')->andReturn('0');
|
|
|
|
self::assertFalse($this->repo->hasGrant(8, 5));
|
|
}
|
|
|
|
public function testFindGrantedOfferingIdsReturnsInts(): void
|
|
{
|
|
$this->db->shouldReceive('prepare')
|
|
->once()
|
|
->with(
|
|
Mockery::pattern('/DISTINCT offering_id.*student_id = %d/s'),
|
|
'wp_us_group_access',
|
|
5,
|
|
GroupAccess::STATUS_INVITED,
|
|
GroupAccess::STATUS_ENROLLED
|
|
)
|
|
->andReturn('SELECT ...');
|
|
$this->db->shouldReceive('get_col')->andReturn(['8', '9']);
|
|
|
|
self::assertSame([8, 9], $this->repo->findGrantedOfferingIds(5));
|
|
}
|
|
|
|
public function testFindByOfferingMapsRows(): void
|
|
{
|
|
$this->db->shouldReceive('prepare')->andReturn('SELECT ...');
|
|
$this->db->shouldReceive('get_results')->andReturn([
|
|
(object) [
|
|
'id' => '1',
|
|
'offering_id' => '8',
|
|
'student_id' => '5',
|
|
'email' => '',
|
|
'invite_id' => null,
|
|
'status' => GroupAccess::STATUS_INVITED,
|
|
'invited_by' => '2',
|
|
],
|
|
]);
|
|
|
|
$grants = $this->repo->findByOffering(8);
|
|
|
|
self::assertCount(1, $grants);
|
|
self::assertSame(5, $grants[0]->studentId);
|
|
}
|
|
|
|
public function testLinkStudentByEmailUpdatesNullStudentRows(): void
|
|
{
|
|
$this->db->shouldReceive('prepare')
|
|
->once()
|
|
->with(
|
|
Mockery::pattern('/UPDATE %i SET student_id = %d WHERE email = %s AND student_id IS NULL/'),
|
|
'wp_us_group_access',
|
|
5,
|
|
'[email protected]'
|
|
)
|
|
->andReturn('UPDATE ...');
|
|
$this->db->shouldReceive('query')->once()->with('UPDATE ...')->andReturn(1);
|
|
|
|
self::assertTrue($this->repo->linkStudentByEmail('[email protected]', 5));
|
|
}
|
|
|
|
public function testLinkStudentByEmailIgnoresEmptyEmail(): void
|
|
{
|
|
$this->db->shouldReceive('prepare')->never();
|
|
|
|
self::assertFalse($this->repo->linkStudentByEmail('', 5));
|
|
}
|
|
|
|
public function testMarkEnrolledUpdatesStatus(): void
|
|
{
|
|
$this->db->shouldReceive('update')
|
|
->once()
|
|
->with(
|
|
'wp_us_group_access',
|
|
['status' => GroupAccess::STATUS_ENROLLED],
|
|
['offering_id' => 8, 'student_id' => 5],
|
|
['%s'],
|
|
['%d', '%d']
|
|
)
|
|
->andReturn(1);
|
|
|
|
self::assertTrue($this->repo->markEnrolled(8, 5));
|
|
}
|
|
|
|
public function testRevokeUpdatesStatus(): void
|
|
{
|
|
$this->db->shouldReceive('update')
|
|
->once()
|
|
->with('wp_us_group_access', ['status' => GroupAccess::STATUS_REVOKED], ['id' => 3], ['%s'], ['%d'])
|
|
->andReturn(1);
|
|
|
|
self::assertTrue($this->repo->revoke(3));
|
|
}
|
|
}
|