update project
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\IslamicSundaySchool\Attendance;
|
||||
|
||||
use App\Domain\IslamicSundaySchool\Attendance\Policies\IslamicSundaySchoolAttendanceStatusPolicy;
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class IslamicSundaySchoolAttendanceStatusPolicyTest extends TestCase
|
||||
{
|
||||
public function test_extension_can_use_different_late_window_without_changing_core(): void
|
||||
{
|
||||
$context = new SchoolContext(1, 10, [], '2026', 'spring', 'UTC', 'en', 'USD', 'islamic_sunday_school');
|
||||
$session = new AttendanceSessionView(1, 1, 'Program Session', 'program', new DateTimeImmutable('2026-05-29 09:00:00'), new DateTimeImmutable('2026-05-29 12:00:00'), 'UTC');
|
||||
$policy = new IslamicSundaySchoolAttendanceStatusPolicy();
|
||||
|
||||
$this->assertSame('present', $policy->statusForScan($context, $session, new DateTimeImmutable('2026-05-29 09:14:00'))->status);
|
||||
$this->assertSame('late', $policy->statusForScan($context, $session, new DateTimeImmutable('2026-05-29 09:16:00'))->status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\IslamicSundaySchool;
|
||||
|
||||
use App\Domain\IslamicSundaySchool\Attendance\IslamicSundaySchoolAttendancePolicy;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class AttendancePolicyTest extends TestCase
|
||||
{
|
||||
public function test_it_treats_sunday_as_school_day(): void
|
||||
{
|
||||
$context = new SchoolContext(1, 2, [], '2025', 'spring', 'UTC', 'en', 'USD', 'islamic_sunday_school');
|
||||
$policy = new IslamicSundaySchoolAttendancePolicy();
|
||||
|
||||
$this->assertTrue($policy->isSchoolDay($context, new \DateTimeImmutable('2026-05-31')));
|
||||
$this->assertFalse($policy->isSchoolDay($context, new \DateTimeImmutable('2026-05-30')));
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace Tests\Unit\IslamicSundaySchool\Communication; use App\Domain\IslamicSundaySchool\Communication\Templates\IslamicSundaySchoolTemplateCatalog; use PHPUnit\Framework\TestCase; final class IslamicSundaySchoolCommunicationExtensionTest extends TestCase { public function test_template_catalog_is_extension_owned(): void { $this->assertArrayHasKey('program_class_reminder',(new IslamicSundaySchoolTemplateCatalog())->templates()); } }
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use App\Domain\IslamicSundaySchool\Reporting\Reports\QuranProgressReport;
|
||||
|
||||
final class IslamicSundaySchoolReportAvailabilityTest extends TestCase
|
||||
{
|
||||
public function test_islamic_report_only_available_for_islamic_profile(): void
|
||||
{
|
||||
$report = new QuranProgressReport();
|
||||
$this->assertTrue($report->availableForDomainProfile('islamic_sunday_school'));
|
||||
$this->assertFalse($report->availableForDomainProfile('standard_school'));
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Tests\Unit\IslamicSundaySchool\Students;
|
||||
use App\Domain\IslamicSundaySchool\Students\Policies\IslamicSundaySchoolStudentIdentifierGenerator; use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Students\DTO\StudentView; use PHPUnit\Framework\TestCase;
|
||||
final class IslamicSundaySchoolStudentIdentifierGeneratorTest extends TestCase { public function test_generates_extension_identifier_without_changing_core(): void { $ctx=new SchoolContext(1,9,[1],'2026','spring','America/New_York','en','USD','islamic_sunday_school'); $id=(new IslamicSundaySchoolStudentIdentifierGenerator())->generateForPersistedStudent($ctx,new StudentView(55,1,null,'A','B')); $this->assertStringStartsWith('SS-',(string)$id); $this->assertStringEndsWith('-000055',(string)$id); } }
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\SchoolCore\Attendance;
|
||||
|
||||
use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView;
|
||||
use App\Domain\SchoolCore\Attendance\Policies\DefaultAttendanceStatusPolicy;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class DefaultAttendanceStatusPolicyTest extends TestCase
|
||||
{
|
||||
public function test_status_becomes_late_after_default_threshold(): void
|
||||
{
|
||||
$context = new SchoolContext(1, 10, [], '2026', 'spring', 'UTC', 'en', 'USD', 'standard_school');
|
||||
$session = new AttendanceSessionView(1, 1, 'Morning', 'daily', new DateTimeImmutable('2026-05-29 09:00:00'), new DateTimeImmutable('2026-05-29 12:00:00'), 'UTC');
|
||||
$policy = new DefaultAttendanceStatusPolicy();
|
||||
|
||||
$this->assertSame('present', $policy->statusForScan($context, $session, new DateTimeImmutable('2026-05-29 09:05:00'))->status);
|
||||
$this->assertSame('late', $policy->statusForScan($context, $session, new DateTimeImmutable('2026-05-29 09:11:00'))->status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\SchoolCore\Attendance;
|
||||
|
||||
use App\Domain\SchoolCore\Attendance\DTO\ProcessScanData;
|
||||
use App\Domain\SchoolCore\Attendance\Support\ScanFingerprint;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ScanFingerprintTest extends TestCase
|
||||
{
|
||||
public function test_scan_fingerprint_is_school_scoped(): void
|
||||
{
|
||||
$scan = new ProcessScanData('ABC123', 'front', 'check_in', 'scanner', new DateTimeImmutable('2026-05-29 09:00:00'));
|
||||
$a = new SchoolContext(1, 10, [], '2026', 'spring', 'UTC', 'en', 'USD', 'standard_school');
|
||||
$b = new SchoolContext(2, 10, [], '2026', 'spring', 'UTC', 'en', 'USD', 'standard_school');
|
||||
|
||||
$this->assertNotSame(
|
||||
ScanFingerprint::fromScan($a, $scan, 90),
|
||||
ScanFingerprint::fromScan($b, $scan, 90)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace Tests\Unit\SchoolCore\Communication; use App\Domain\SchoolCore\Communication\DTO\RecipientData; use App\Domain\SchoolCore\Communication\Services\RecipientDedupeService; use App\Domain\SchoolCore\Communication\Support\RecipientSet; use PHPUnit\Framework\TestCase; final class RecipientDedupeServiceTest extends TestCase { public function test_dedupes_same_contact(): void { $r=new RecipientData('guardian',1,'Guardian','same@example.test',null,null,[],null,null,10,['email']); $result=(new RecipientDedupeService())->dedupe(new RecipientSet([$r,$r])); $this->assertSame(1,$result->count()); $this->assertNotEmpty($result->excluded); } }
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\SchoolCore\Context;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class SchoolContextTest extends TestCase
|
||||
{
|
||||
public function test_it_builds_audit_payload(): void
|
||||
{
|
||||
$context = new SchoolContext(
|
||||
schoolId: 12,
|
||||
actorUserId: 44,
|
||||
actorRoleIds: [1, 2],
|
||||
schoolYear: '2025-2026',
|
||||
term: 'spring',
|
||||
timezone: 'America/New_York',
|
||||
locale: 'en',
|
||||
currency: 'USD',
|
||||
domainProfile: SchoolContext::DOMAIN_ISLAMIC_SUNDAY_SCHOOL,
|
||||
);
|
||||
|
||||
$this->assertTrue($context->isIslamicSundaySchool());
|
||||
$this->assertSame(12, $context->auditPayload()['school_id']);
|
||||
$this->assertSame('spring', $context->term);
|
||||
$this->assertSame('fall', $context->withTerm('fall')->term);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\SchoolCore\Context;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Context\SchoolContextException;
|
||||
use App\Domain\SchoolCore\Context\SchoolContextValidator;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class SchoolContextValidatorTest extends TestCase
|
||||
{
|
||||
public function test_it_rejects_invalid_timezone(): void
|
||||
{
|
||||
$this->expectException(SchoolContextException::class);
|
||||
|
||||
$validator = new SchoolContextValidator();
|
||||
$validator->assertValid($this->context(timezone: 'Not/AZone'), new FakeActor(10, 99));
|
||||
}
|
||||
|
||||
public function test_it_rejects_wrong_school_actor(): void
|
||||
{
|
||||
$this->expectException(SchoolContextException::class);
|
||||
|
||||
$validator = new SchoolContextValidator();
|
||||
$validator->assertValid($this->context(schoolId: 20), new FakeActor(10, 99));
|
||||
}
|
||||
|
||||
private function context(int $schoolId = 10, string $timezone = 'UTC'): SchoolContext
|
||||
{
|
||||
return new SchoolContext(
|
||||
schoolId: $schoolId,
|
||||
actorUserId: 99,
|
||||
actorRoleIds: [],
|
||||
schoolYear: '2025-2026',
|
||||
term: 'spring',
|
||||
timezone: $timezone,
|
||||
locale: 'en',
|
||||
currency: 'USD',
|
||||
domainProfile: SchoolContext::DOMAIN_STANDARD_SCHOOL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class FakeActor implements Authenticatable
|
||||
{
|
||||
public function __construct(public int $school_id, private int $id)
|
||||
{
|
||||
}
|
||||
|
||||
public function getAuthIdentifierName(): string { return 'id'; }
|
||||
public function getAuthIdentifier(): mixed { return $this->id; }
|
||||
public function getAuthPassword(): string { return ''; }
|
||||
public function getAuthPasswordName(): string { return 'password'; }
|
||||
public function getRememberToken(): ?string { return null; }
|
||||
public function setRememberToken($value): void {}
|
||||
public function getRememberTokenName(): string { return 'remember_token'; }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\SchoolCore\Finance;
|
||||
|
||||
use App\Domain\SchoolCore\Finance\Services\BalanceCalculator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class BalanceCalculatorTest extends TestCase
|
||||
{
|
||||
public function test_payment_reduces_balance(): void
|
||||
{
|
||||
$result = (new BalanceCalculator())->calculate(['currency'=>'USD'], [['amount_minor'=>10000,'type'=>'charge']], [['amount_minor'=>4000,'status'=>'recorded']]);
|
||||
$this->assertSame(10000, $result['total_amount_minor']);
|
||||
$this->assertSame(4000, $result['paid_amount_minor']);
|
||||
$this->assertSame(6000, $result['balance_amount_minor']);
|
||||
$this->assertSame('partial', $result['status']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\SchoolCore\Finance;
|
||||
|
||||
use App\Domain\SchoolCore\Finance\Support\Money;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class MoneyTest extends TestCase
|
||||
{
|
||||
public function test_money_adds_and_compares_without_float_math(): void
|
||||
{
|
||||
$a = new Money(12500, 'USD'); $b = new Money(2500, 'USD');
|
||||
$this->assertSame(15000, $a->add($b)->minorAmount());
|
||||
$this->assertSame(10000, $a->subtract($b)->minorAmount());
|
||||
$this->assertSame(1, $a->compare($b));
|
||||
$this->assertSame('125.00', $a->toDecimalString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use App\Domain\SchoolCore\Reporting\Services\ReportRegistry;
|
||||
use App\Domain\SchoolCore\Reporting\Reports\Students\StudentRosterReport;
|
||||
|
||||
final class ReportRegistryTest extends TestCase
|
||||
{
|
||||
public function test_registers_and_returns_report_by_key(): void
|
||||
{
|
||||
$registry = new ReportRegistry([new StudentRosterReport()]);
|
||||
$this->assertSame('students.roster', $registry->get('students.roster')->key());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Tests\Unit\SchoolCore\Students;
|
||||
use App\Domain\SchoolCore\Context\SchoolContext; use App\Domain\SchoolCore\Students\DTO\StudentView; use App\Domain\SchoolCore\Students\Policies\DefaultStudentIdentifierGenerator; use PHPUnit\Framework\TestCase;
|
||||
final class DefaultStudentIdentifierGeneratorTest extends TestCase { public function test_generates_identifier_from_persisted_student_id(): void { $ctx=new SchoolContext(1,9,[1],'2026','spring','America/New_York','en','USD','standard_school'); $id=(new DefaultStudentIdentifierGenerator())->generateForPersistedStudent($ctx,new StudentView(123,1,null,'A','B')); $this->assertSame('SCH-0000123',(string)$id); } }
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\SchoolCore\Students;
|
||||
|
||||
use App\Domain\SchoolCore\Context\SchoolContext;
|
||||
use App\Domain\SchoolCore\Students\Services\SequentialStudentIdentifierGenerator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class SequentialStudentIdentifierGeneratorTest extends TestCase
|
||||
{
|
||||
public function test_it_generates_identifier_after_student_id_exists(): void
|
||||
{
|
||||
$context = new SchoolContext(12, 44, [], '2025', 'spring', 'UTC', 'en', 'USD', 'standard_school');
|
||||
$identifier = (new SequentialStudentIdentifierGenerator())->generate($context, 991);
|
||||
$this->assertSame('12-000991', $identifier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Tests\Unit\SchoolCore\Students;
|
||||
use App\Domain\SchoolCore\Students\Exceptions\InvalidStudentStatusTransitionException; use App\Domain\SchoolCore\Students\Support\StudentLifecycleStateMachine; use PHPUnit\Framework\TestCase;
|
||||
final class StudentLifecycleStateMachineTest extends TestCase { public function test_allows_valid_transition(): void { (new StudentLifecycleStateMachine())->assertCanTransition('active','withdrawn'); $this->assertTrue(true); } public function test_rejects_invalid_transition(): void { $this->expectException(InvalidStudentStatusTransitionException::class); (new StudentLifecycleStateMachine())->assertCanTransition('archived','active'); } }
|
||||
Reference in New Issue
Block a user