[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', 'app.timezone' => 'Asia/Shanghai', ]); DB::purge('business_mirror'); Schema::connection('business_mirror')->create('business_mirror_table_states', function (Blueprint $table): void { $table->string('dataset_key', 191)->primary(); $table->string('source_key', 64); $table->string('source_object', 191); $table->string('target_table', 64); $table->string('status', 24)->default('pending'); $table->string('run_mode', 24)->nullable(); $table->json('cursor')->nullable(); $table->json('high_watermark')->nullable(); $table->dateTime('window_started_at')->nullable(); $table->dateTime('window_ends_at')->nullable(); $table->dateTime('initial_completed_at')->nullable(); $table->dateTime('last_attempted_at')->nullable(); $table->dateTime('last_succeeded_at')->nullable(); $table->unsignedBigInteger('last_upserted_rows')->default(0); $table->unsignedBigInteger('mirror_row_count')->default(0); $table->string('last_error_code', 120)->nullable(); $table->timestamps(); }); (require base_path('database/migrations/2026_11_04_230000_create_ding_worklog_reports.php'))->up(); $this->repository = app(DingWorklogMirrorRepository::class); } public function test_submissions_before_noon_belong_to_the_previous_statistics_day(): void { $this->assertSame( '2026-11-03', DingWorklogMirrorRepository::statisticsDate('2026-11-04 10:26:00')->toDateString(), ); $this->assertSame( '2026-11-04', DingWorklogMirrorRepository::statisticsDate('2026-11-05 11:59:59')->toDateString(), ); $this->assertSame( '2026-11-03', DingWorklogMirrorRepository::statisticsDate( Carbon::parse('2026-11-04 10:26:00', 'Asia/Shanghai')->getTimestampMs(), )->toDateString(), ); } public function test_noon_is_the_cutoff_for_the_next_statistics_day(): void { $this->assertSame( '2026-11-05', DingWorklogMirrorRepository::statisticsDate('2026-11-05 12:00:00')->toDateString(), ); } public function test_daily_summary_and_details_use_the_saved_statistics_date(): void { Carbon::setTestNow(Carbon::parse('2026-11-05 15:00:00', 'Asia/Shanghai')); try { $now = now(); $this->repository->upsertReports([ [ 'report_id' => 'report-a', 'template_name' => '财务日报', 'creator_userid' => 'user-a', 'creator_name' => '秋香', 'submitted_at' => '2026-11-04 10:26:00', 'statistics_date' => '2026-11-03', 'remark' => null, 'raw_payload' => '{}', 'synced_at' => $now, 'created_at' => $now, 'updated_at' => $now, ], [ 'report_id' => 'report-b', 'template_name' => '财务日报', 'creator_userid' => 'user-b', 'creator_name' => '李斯', 'submitted_at' => '2026-11-04 11:20:00', 'statistics_date' => '2026-11-03', 'remark' => null, 'raw_payload' => '{}', 'synced_at' => $now, 'created_at' => $now, 'updated_at' => $now, ], [ 'report_id' => 'report-c', 'template_name' => '财务日报', 'creator_userid' => 'user-a', 'creator_name' => '秋香', 'submitted_at' => '2026-11-05 12:00:00', 'statistics_date' => '2026-11-05', 'remark' => null, 'raw_payload' => '{}', 'synced_at' => $now, 'created_at' => $now, 'updated_at' => $now, ], ]); $this->repository->recordSuccess( 3, '财务日报', Carbon::parse('2026-10-01 00:00:00', 'Asia/Shanghai'), $now, ); $summary = $this->repository->dailySummary('2026-11-03', '2026-11-05'); $this->assertSame([ [ 'statistics_date' => '2026-11-05', 'submission_count' => 1, 'submitter_count' => 1, 'first_submitted_at' => '2026-11-05 12:00:00', 'last_submitted_at' => '2026-11-05 12:00:00', ], [ 'statistics_date' => '2026-11-03', 'submission_count' => 2, 'submitter_count' => 2, 'first_submitted_at' => '2026-11-04 10:26:00', 'last_submitted_at' => '2026-11-04 11:20:00', ], ], $summary); $details = $this->repository->submissionsForDate('2026-11-03'); $this->assertSame(['report-b', 'report-a'], array_column($details, 'report_id')); $this->assertSame('succeeded', $this->repository->state()['status']); $this->assertSame(3, (int) $this->repository->state()['mirror_row_count']); } finally { Carbon::setTestNow(); } } public function test_permission_failure_is_exposed_to_the_page(): void { $this->repository->recordFailure('dingtalk_worklog_permission_required'); $state = $this->repository->state(); $this->assertSame('failed', $state['status']); $this->assertSame('dingtalk_worklog_permission_required', $state['last_error_code']); $this->assertTrue(DingWorklogMirrorRepository::isPermissionError($state['last_error_code'])); } }