Files
clinicpro/tests/Shared/TenantLookupInventoryTest.php
T
hamedandClaude Opus 5 6afc5c090e refactor(booking): extract ServiceBookingCalculator from the controller
"Allowed duration of a service combination" lived inside
AppointmentController::serviceSlots(). Three upcoming callers need the same
computation (PATCH duration validation, service-aware reschedule, reserve
conversion); copying it would mean four variants with four different edge-case
behaviours.

The extraction is behaviour-preserving: BaseController::error() and
ExceptionSubscriber emit an identical envelope, so returning $this->error() was
replaced by throwing AppException with the same code/message/field.

Tenant ownership now goes through TenantOwnershipChecker::belongsToPair() (the
documented single point) instead of an inline section pair comparison. The repo
property is named itemRepo on purpose: TenantLookupInventoryTest only counts
recognised property names, so any other name would slip past the safety net.

The naive duration sum is kept deliberately — switching to solo/additional
minutes is task 04 and changes one line here.

Task: docs/new_feture/taskes/task-00-service-mode-completion/
Slot-mode contract: unchanged (--group=slot-mode-frozen green)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 12:41:13 +03:30

110 lines
5.3 KiB
PHP

<?php
namespace App\Tests\Shared;
use PHPUnit\Framework\TestCase;
/**
* فهرستِ بازبینی‌شدهٔ جست‌وجوهای «uuid از درخواست → موجودیت محیط‌دار».
*
* سه نشتی این فاز همگی همین شکل را داشتند و هیچ‌کدام در repository نبودند؛ در
* کنترلر و سرویس بودند، جایی که uuid از بدنه/کوئری می‌آید و کسی محیط را نمی‌سنجد.
* پس گارد هم باید همان‌جا باشد، نه روی DQLهای repository.
*
* قاعده: عدد هر فایل = تعداد findByUuid روی repositoryهای محیط‌دار در آن فایل، که
* همگی بازبینی شده‌اند. اگر عدد عوض شود یعنی جست‌وجوی تازه‌ای اضافه شده و باید
* ثابت شود محیطش بررسی می‌شود — با ownsRecord/ownsSection/ownedItem،
* TenantOwnershipChecker، یا لنگرزدن به ریشه‌ای که خودش بررسی شده.
*
* @see \App\Shared\Tenant\TenantOwnershipChecker
* @see docs/architecture/tenancy.md
*/
class TenantLookupInventoryTest extends TestCase
{
/** repositoryهایی که موجودیت محیط‌دار یا فرزند aggregate برمی‌گردانند. */
private const TENANT_REPOSITORY_VARIABLES = [
'itemRepo', 'serviceItemRepo', 'sectionRepo', 'staffRepo',
'noteRepo', 'attachmentRepo', 'callRepo', 'messageRepo', 'medicalRepo',
'sessionRepo', 'sessionPaymentRepo', 'recordRepo',
'tenantInsuranceRepo', 'tenantTagRepo', 'packageRepo', 'ruleRepo',
];
/**
* فایل => تعداد جست‌وجوی بازبینی‌شده.
*
* @var array<string, int>
*/
private const REVIEWED = [
// assertServicesMatchContext (مسیر ثبت نوبت) + خودِ گارد. جست‌وجوی مسیرِ اسلاتِ
// سرویسی به ServiceBookingCalculator منتقل شد و همان‌جا شمرده می‌شود.
'src/Appointment/Controller/AppointmentController.php' => 2,
// TenantOwnershipChecker::belongsToPair() روی هر uuid، پیش از هر بررسی دیگر
'src/Appointment/Service/ServiceBookingCalculator.php' => 2,
// TenantOwnershipChecker روی بخش/سرویس/پرسنل و فهرست سرویس‌ها
'src/Appointment/Controller/MyAppointmentsController.php' => 1,
// ownsSession / getEntityType روی صورتحساب، پرونده و مطالبه
'src/Billing/Controller/BillingController.php' => 3,
// ownsSection ×۹ و مقایسهٔ مستقیم جفت ×۳ (پکیج، کالا، پرسنل)
'src/ClinicService/Controller/ClinicServiceController.php' => 10,
'src/Discount/Controller/DiscountController.php' => 1,
// قرارداد بیمه با جفت، و سرویس با getSection()->getEntityType()
'src/Insurance/Controller/InsuranceController.php' => 5,
// ownedItem() / ownedPackage()
'src/Inventory/Controller/InventoryController.php' => 2,
'src/Inventory/Service/InventoryService.php' => 1,
// ownsRecord ×۳۳ و دو پرداختِ لنگرخورده به مراجعهٔ بررسی‌شده
'src/Patient/Controller/PatientController.php' => 35,
// TenantOwnershipChecker در هر سه حلقه (قیمت‌گذاری، ساخت، ویرایش) + کالا
'src/Patient/Service/PatientService.php' => 5,
// ownsStaff()
'src/Staff/Controller/StaffController.php' => 2,
];
private function projectDir(): string
{
return dirname(__DIR__, 2);
}
/** @return array<string, int> */
private function countLookups(): array
{
$pattern = '/->(' . implode('|', self::TENANT_REPOSITORY_VARIABLES) . ')->findByUuid\(/';
$counts = [];
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($this->projectDir() . '/src', \FilesystemIterator::SKIP_DOTS),
);
foreach ($files as $file) {
if ($file->getExtension() !== 'php') {
continue;
}
$found = preg_match_all($pattern, (string) file_get_contents($file->getPathname()));
if ($found > 0) {
$relative = str_replace($this->projectDir() . '/', '', $file->getPathname());
$counts[$relative] = $found;
}
}
ksort($counts);
return $counts;
}
public function testNoUnreviewedTenantLookupExists(): void
{
$actual = $this->countLookups();
$expected = self::REVIEWED;
ksort($expected);
self::assertSame($expected, $actual, <<<'MSG'
جست‌وجوی «uuid از درخواست موجودیت محیط‌دار» تغییر کرده.
برای هر مورد تازه ثابت کن محیطش بررسی می‌شود (ownsRecord/ownsSection/
ownedItem، TenantOwnershipChecker، یا لنگر به ریشهٔ بررسی‌شده) و بعد عدد
همان فایل را در REVIEWED به‌روز کن. اگر عدد کم شده، فقط عدد را کم کن.
MSG);
}
}