reviewed epic 3
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user