subject = new class { use FormatsBoardDate; public function callFormatCreatedAtFormat(mixed $dateTime, string $format = 'standard'): string { return $this->formatCreatedAtFormat($dateTime, $format); } public function callFormatCreatedAt(mixed $dateTime): string { return $this->formatCreatedAt($dateTime); } }; } // ========================================================================= // formatCreatedAtFormat() — 표준형 (standard) // ========================================================================= #[Test] public function standard_format_returns_방금전_when_less_than_1_minute(): void { $dateTime = Carbon::now()->subSeconds(30); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'standard'); $this->assertEquals('방금 전', $result); } #[Test] public function standard_format_returns_N분전_when_less_than_10_minutes(): void { $dateTime = Carbon::now()->subMinutes(5); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'standard'); $this->assertEquals('5분 전', $result); } #[Test] public function standard_format_returns_rounded_10분단위_when_10_minutes_or_more(): void { $dateTime = Carbon::now()->subMinutes(21); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'standard'); $this->assertEquals('20분 전', $result); } #[Test] public function standard_format_returns_N시간전_when_less_than_24_hours(): void { $dateTime = Carbon::now()->subHours(3); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'standard'); $this->assertEquals('3시간 전', $result); } #[Test] public function standard_format_returns_MM_DD_when_same_year(): void { $dateTime = Carbon::now()->subDays(5); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'standard'); // trait 가 사용자 타임존(예: Asia/Seoul) 으로 변환한 후 포맷하므로, 기대값도 // 동일 변환을 거쳐야 UTC↔Seoul 9시간 boundary 에서 ±1일 어긋나지 않음 $expectedDate = TimezoneHelper::toUserCarbon($dateTime)->format('m-d'); $this->assertEquals($expectedDate, $result); } #[Test] public function standard_format_returns_YY_MM_DD_when_previous_year(): void { $dateTime = Carbon::now()->subYears(2); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'standard'); // 동일 이유 — 사용자 타임존 변환 후 포맷 $expectedDate = TimezoneHelper::toUserCarbon($dateTime)->format('y-m-d'); $this->assertEquals($expectedDate, $result); } // ========================================================================= // formatCreatedAtFormat() — 유동형 (relative) // ========================================================================= #[Test] public function relative_format_returns_N일전_when_few_days_ago(): void { $dateTime = Carbon::now()->subDays(3); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'relative'); $this->assertEquals('3일 전', $result); } #[Test] public function relative_format_returns_N개월전_when_months_ago(): void { // Carbon::diffInMonths() 는 일수 차가 한 달 미만일 때 내림 처리하므로 // 월말/월초 boundary 에서 subMonths(2) 만으로는 1개월로 평가될 수 있다. // 안정적인 2개월 차이를 보장하기 위해 추가로 며칠을 더 뺀다. $dateTime = Carbon::now()->subMonths(2)->subDays(5); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'relative'); $this->assertEquals('2개월 전', $result); } #[Test] public function relative_format_returns_N년전_when_years_ago(): void { $dateTime = Carbon::now()->subYears(3); $result = $this->subject->callFormatCreatedAtFormat($dateTime, 'relative'); $this->assertEquals('3년 전', $result); } // ========================================================================= // formatCreatedAtFormat() — 엣지 케이스 // ========================================================================= #[Test] public function format_returns_empty_string_when_null(): void { $result = $this->subject->callFormatCreatedAtFormat(null); $this->assertEquals('', $result); } #[Test] public function format_accepts_string_datetime(): void { $dateString = Carbon::now()->subDays(5)->format('Y-m-d H:i:s'); $result = $this->subject->callFormatCreatedAtFormat($dateString, 'standard'); $this->assertNotEmpty($result); } // ========================================================================= // formatCreatedAt() — 전체 날짜+시간 포맷 (Y-m-d H:i:s, 사용자 타임존) // 코어 BaseApiResource::formatDateTimeStringForUser() / 이커머스 주문일시와 동일 // ========================================================================= #[Test] public function created_at_returns_full_datetime_string(): void { // UTC 기준으로 Carbon 생성 후 결과 포맷만 검증 (타임존 변환은 TimezoneHelper가 처리) $dateTime = Carbon::now(); $result = $this->subject->callFormatCreatedAt($dateTime); // YYYY-MM-DD HH:MM:SS 형식인지 검증 (요일 미포함) $this->assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $result); } #[Test] public function created_at_matches_timezone_helper_output(): void { // 코어 TimezoneHelper::toUserDateTimeString() 와 동일한 결과를 내야 한다 $dateTime = Carbon::create(2026, 3, 22, 12, 0, 0, 'UTC'); $result = $this->subject->callFormatCreatedAt($dateTime); $this->assertEquals(TimezoneHelper::toUserDateTimeString($dateTime), $result); } #[Test] public function created_at_returns_empty_string_when_null(): void { $result = $this->subject->callFormatCreatedAt(null); $this->assertEquals('', $result); } #[Test] public function created_at_accepts_string_datetime(): void { $result = $this->subject->callFormatCreatedAt('2026-03-18 14:30:00'); $this->assertStringContainsString('2026-03-18', $result); $this->assertMatchesRegularExpression('/\d{2}:\d{2}:\d{2}$/', $result); } #[Test] public function created_at_format_matches_expected_pattern(): void { $dateTime = Carbon::create(2026, 1, 5, 9, 5, 0); $result = $this->subject->callFormatCreatedAt($dateTime); // "YYYY-MM-DD HH:MM:SS" 형식 검증 (요일 미포함) $this->assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $result); } }