[ '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_10_120000_create_ding_attendance_schedule_records.php'))->up(); $roster = Mockery::mock(FinanceRoster::class); $roster->shouldReceive('cachedFinanceMembers')->once()->andReturn([ ['userid' => '1004216768778351', 'name' => '人参'], ]); $this->app->instance(FinanceRoster::class, $roster); $path = tempnam(sys_get_temp_dir(), 'ding-schedule-'); file_put_contents($path, json_encode([ 'success' => true, 'result' => [ [ 'id' => 'schedule-work', 'userId' => '1004216768778351', 'workDate' => 1793980800000, 'groupId' => 967215034, 'classId' => 123, 'isRest' => 'N', 'checkStatus' => 'Init', 'timeChanged' => false, ], [ 'id' => 'schedule-rest', 'userId' => '1004216768778351', 'workDate' => 1794067200000, 'groupId' => 967215034, 'isRest' => 'Y', 'checkStatus' => 'Init', 'timeChanged' => false, ], ], ], JSON_UNESCAPED_UNICODE)); try { $this->artisan('business-mirror:import-ding-attendance-schedules', [ '--file' => $path, '--from' => '2026-11-07', '--to' => '2026-11-08', ])->expectsOutputToContain('1 人、2 天、2 条排班')->assertSuccessful(); $rows = DB::connection('business_mirror') ->table('ding_attendance_schedule_records') ->orderBy('work_date') ->get(['user_id', 'user_name', 'work_date', 'is_rest']) ->map(static fn (object $row): array => (array) $row) ->all(); $this->assertSame([ ['user_id' => '1004216768778351', 'user_name' => '人参', 'work_date' => '2026-11-07', 'is_rest' => 'N'], ['user_id' => '1004216768778351', 'user_name' => '人参', 'work_date' => '2026-11-08', 'is_rest' => 'Y'], ], $rows); $this->assertSame( 'succeeded', DB::connection('business_mirror')->table('business_mirror_table_states') ->where('dataset_key', 'dingtalk.attendance.finance_schedules') ->value('status'), ); } finally { @unlink($path); } } }