reviewed epic 3

This commit is contained in:
Naser Mansour
2025-12-20 21:25:58 +02:00
parent b93b9363a6
commit 8b8d9735b9
8 changed files with 2116 additions and 24 deletions
@@ -16,6 +16,30 @@ So that **I can easily add the consultation to my calendar app**.
- **Follows pattern:** Service class for generation
- **Touch points:** Approval email, client dashboard download
### Required Consultation Model Fields
This story assumes the following fields exist on the `Consultation` model (from previous stories):
- `id` - Unique identifier (used as booking reference)
- `user_id` - Foreign key to User
- `scheduled_date` - Date of consultation
- `scheduled_time` - Time of consultation
- `duration` - Duration in minutes (default: 45)
- `status` - Consultation status (must be 'approved' for .ics generation)
- `type` - 'free' or 'paid'
- `payment_amount` - Amount for paid consultations (nullable)
### Configuration Requirement
Create `config/libra.php` with office address:
```php
<?php
return [
'office_address' => [
'ar' => 'مكتب ليبرا للمحاماة، فلسطين',
'en' => 'Libra Law Firm, Palestine',
],
];
```
## Acceptance Criteria
### Calendar File Generation
@@ -52,6 +76,15 @@ So that **I can easily add the consultation to my calendar app**.
- [ ] Valid iCalendar format (passes validators)
- [ ] Tests for file generation
- [ ] Tests for calendar app compatibility
- [ ] Tests for bilingual content (Arabic/English)
- [ ] Tests for download route authorization
- [ ] Tests for email attachment
### Edge Cases to Handle
- User with null `preferred_language` defaults to 'ar'
- Duration defaults to 45 minutes if not set on consultation
- Escape special characters (commas, semicolons, backslashes) in .ics content
- Ensure proper CRLF line endings per RFC 5545
## Technical Notes
@@ -242,6 +275,7 @@ Route::middleware(['auth'])->group(function () {
```php
use App\Services\CalendarService;
use App\Models\Consultation;
use App\Models\User;
it('generates valid ics file', function () {
$consultation = Consultation::factory()->approved()->create([
@@ -276,6 +310,99 @@ it('includes correct duration', function () {
->toContain('DTSTART;TZID=Asia/Jerusalem:20240315T100000')
->toContain('DTEND;TZID=Asia/Jerusalem:20240315T104500');
});
it('generates Arabic content for Arabic-preferring users', function () {
$user = User::factory()->create(['preferred_language' => 'ar']);
$consultation = Consultation::factory()->approved()->for($user)->create();
$service = new CalendarService();
$ics = $service->generateIcs($consultation);
expect($ics)
->toContain('استشارة مع مكتب ليبرا للمحاماة')
->toContain('رقم الحجز');
});
it('generates English content for English-preferring users', function () {
$user = User::factory()->create(['preferred_language' => 'en']);
$consultation = Consultation::factory()->approved()->for($user)->create();
$service = new CalendarService();
$ics = $service->generateIcs($consultation);
expect($ics)
->toContain('Consultation with Libra Law Firm')
->toContain('Booking Reference');
});
it('includes payment info for paid consultations', function () {
$consultation = Consultation::factory()->approved()->create([
'type' => 'paid',
'payment_amount' => 150.00,
]);
$service = new CalendarService();
$ics = $service->generateIcs($consultation);
expect($ics)->toContain('150.00');
});
it('includes 1-hour reminder alarm', function () {
$consultation = Consultation::factory()->approved()->create();
$service = new CalendarService();
$ics = $service->generateIcs($consultation);
expect($ics)
->toContain('BEGIN:VALARM')
->toContain('TRIGGER:-PT1H')
->toContain('END:VALARM');
});
it('returns download response with correct headers', function () {
$consultation = Consultation::factory()->approved()->create([
'scheduled_date' => '2024-03-15',
]);
$service = new CalendarService();
$response = $service->generateDownloadResponse($consultation);
expect($response->headers->get('Content-Type'))
->toBe('text/calendar; charset=utf-8');
expect($response->headers->get('Content-Disposition'))
->toContain('consultation-2024-03-15.ics');
});
it('prevents unauthorized users from downloading calendar file', function () {
$owner = User::factory()->create();
$other = User::factory()->create();
$consultation = Consultation::factory()->approved()->for($owner)->create();
$this->actingAs($other)
->get(route('client.consultations.calendar', $consultation))
->assertForbidden();
});
it('prevents download for non-approved consultations', function () {
$user = User::factory()->create();
$consultation = Consultation::factory()->for($user)->create([
'status' => 'pending',
]);
$this->actingAs($user)
->get(route('client.consultations.calendar', $consultation))
->assertNotFound();
});
it('allows owner to download calendar file', function () {
$user = User::factory()->create();
$consultation = Consultation::factory()->approved()->for($user)->create();
$this->actingAs($user)
->get(route('client.consultations.calendar', $consultation))
->assertOk()
->assertHeader('Content-Type', 'text/calendar; charset=utf-8');
});
```
## Definition of Done