You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wx.sstbc.com/tests/Feature/SchoolmateEnrollmentCommand...

157 lines
7.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class SchoolmateEnrollmentCommandTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
config(['database.default' => 'sqlite', 'database.connections.sqlite.database' => ':memory:', 'audit.enabled' => false]);
DB::purge('sqlite');
Carbon::setTestNow('2026-10-15 10:00:00');
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name')->default('测试学员');
$table->string('mobile')->nullable();
$table->integer('is_schoolmate')->nullable();
$table->dateTime('schoolmate_time')->nullable();
$table->timestamps();
$table->softDeletes();
});
Schema::create('courses', function (Blueprint $table) {
$table->id();
$table->string('name')->default('测试课程');
$table->date('start_date')->nullable();
$table->integer('auto_schoolmate')->default(0);
$table->softDeletes();
});
Schema::create('course_signs', function (Blueprint $table) {
$table->id();
$table->integer('course_id');
$table->integer('user_id');
$table->integer('status')->default(1);
$table->softDeletes();
});
}
protected function tearDown(): void
{
Carbon::setTestNow();
parent::tearDown();
}
private function course(int $id, ?string $date, int $auto = 0): void
{
DB::table('courses')->insert(['id' => $id, 'start_date' => $date, 'auto_schoolmate' => $auto]);
}
private function sign(int $userId, int $courseId, int $status = 1, ?string $deleted = null): void
{
DB::table('course_signs')->insert(['user_id' => $userId, 'course_id' => $courseId, 'status' => $status, 'deleted_at' => $deleted]);
}
public function test_preview_does_not_write_and_execute_is_idempotent_and_preserves_existing_schoolmates(): void
{
$this->course(1, '2025-06-01');
$this->course(2, '2024-03-01');
DB::table('users')->insert([
['id' => 1, 'is_schoolmate' => null, 'schoolmate_time' => null, 'deleted_at' => null],
['id' => 2, 'is_schoolmate' => 1, 'schoolmate_time' => '2020-01-01 00:00:00', 'deleted_at' => null],
['id' => 3, 'is_schoolmate' => 0, 'schoolmate_time' => null, 'deleted_at' => '2025-01-01 00:00:00'],
['id' => 4, 'is_schoolmate' => 0, 'schoolmate_time' => null, 'deleted_at' => null],
['id' => 5, 'is_schoolmate' => 0, 'schoolmate_time' => null, 'deleted_at' => null],
]);
foreach ([1, 2, 3] as $id) $this->sign($id, 1);
$this->sign(1, 2);
$this->sign(1, 2); // 同人重复报名只补一次
$this->sign(4, 1, 0);
$this->sign(5, 1, 1, '2025-01-01 00:00:00');
$this->artisan('schoolmate:backfill', ['course_ids' => '1,2'])->expectsOutputToContain('发现遗漏 1 人')->assertExitCode(0);
$this->assertNull(DB::table('users')->where('id', 1)->value('is_schoolmate'));
User::withoutEvents(function () {
$this->artisan('schoolmate:backfill', ['course_ids' => '1,2', '--execute' => true])->expectsOutputToContain('实际补齐 1 人')->assertExitCode(0);
});
$this->assertSame('2024-03-01 00:00:00', DB::table('users')->where('id', 1)->value('schoolmate_time'));
$this->assertSame('2020-01-01 00:00:00', DB::table('users')->where('id', 2)->value('schoolmate_time'));
foreach ([3, 4, 5] as $id) $this->assertEquals(0, DB::table('users')->where('id', $id)->value('is_schoolmate'));
$this->artisan('schoolmate:backfill', ['course_ids' => '1,2', '--execute' => true])->expectsOutputToContain('实际补齐 0 人')->assertExitCode(0);
}
public function test_automatic_enrollment_catches_history_and_late_approval_but_respects_course_switch_and_date(): void
{
$this->course(1, '2024-01-01', 1);
$this->course(2, '2026-10-16', 1);
$this->course(3, '2026-10-17', 1);
$this->course(4, '2024-01-01', 0);
foreach ([1, 2, 3, 4, 5] as $id) {
DB::table('users')->insert(['id' => $id, 'is_schoolmate' => 0]);
$this->sign($id, min($id, 4), $id === 5 ? 0 : 1);
}
User::withoutEvents(function () {
$this->artisan('auto_schoolmate')->expectsOutputToContain('共补齐 2 位')->assertExitCode(0);
// 开课后才通过审核的历史课程报名,下次任务仍能处理。
$this->sign(5, 1);
$this->artisan('auto_schoolmate')->expectsOutputToContain('共补齐 1 位')->assertExitCode(0);
});
foreach ([1, 2, 5] as $id) $this->assertEquals(1, DB::table('users')->where('id', $id)->value('is_schoolmate'));
foreach ([3, 4] as $id) $this->assertEquals(0, DB::table('users')->where('id', $id)->value('is_schoolmate'));
}
public function test_invalid_missing_deleted_or_undated_courses_abort_without_partial_updates(): void
{
$this->course(1, '2024-01-01');
$this->course(2, null);
$this->course(3, '2024-01-01');
DB::table('courses')->where('id', 3)->update(['deleted_at' => '2025-01-01']);
DB::table('users')->insert(['id' => 1, 'is_schoolmate' => 0]);
$this->sign(1, 1);
foreach (['1,0', '1,999', '1,2', '1,3'] as $ids) {
$this->artisan('schoolmate:backfill', ['course_ids' => $ids, '--execute' => true])->assertExitCode(1);
}
$this->assertEquals(0, DB::table('users')->where('id', 1)->value('is_schoolmate'));
}
public function test_report_includes_matching_courses_and_schoolmates_without_approved_selected_enrollment(): void
{
$this->course(1, '2024-01-01');
$this->course(2, '2025-02-01');
DB::table('courses')->where('id', 1)->update(['name' => '指定课程甲']);
DB::table('courses')->where('id', 2)->update(['name' => '其他课程乙']);
foreach ([1, 2, 3, 4, 5, 6, 7] as $id) {
DB::table('users')->insert(['id' => $id, 'name' => '学员' . $id,
'is_schoolmate' => $id === 1 ? 0 : 1,
'deleted_at' => $id === 6 ? '2025-01-01 00:00:00' : null]);
}
$this->sign(1, 1);
$this->sign(2, 2); // 其他课程校友应列入反向名单
$this->sign(3, 1, 0); // 仅待审核也需列出
$this->sign(4, 1, 4); // 仅已取消也需列出
$this->sign(5, 1, 1, '2025-01-01 00:00:00'); // 已删除报名不算有效记录
$this->sign(7, 1, 0);
$this->sign(7, 1, 1); // 同时有通过记录,应排除
$exit = \Illuminate\Support\Facades\Artisan::call('schoolmate:backfill', ['course_ids' => '1']);
$output = \Illuminate\Support\Facades\Artisan::output();
$this->assertSame(0, $exit);
$this->assertStringContainsString('1/指定课程甲/2024-01-01', $output);
$this->assertStringContainsString('2/其他课程乙/2025-02-01', $output);
$this->assertStringContainsString('所选课程无审核通过记录的校友共 4 人', $output);
$this->assertStringContainsString('学员2', $output);
$this->assertStringContainsString('学员5', $output);
$this->assertStringContainsString('学员3', $output);
$this->assertStringContainsString('学员4', $output);
$this->assertStringContainsString('1/指定课程甲/待审核', $output);
$this->assertStringContainsString('1/指定课程甲/已取消', $output);
foreach ([6, 7] as $id) $this->assertStringNotContainsString('学员' . $id, $output);
$this->assertEquals(0, DB::table('users')->where('id', 1)->value('is_schoolmate'));
$this->assertEquals(6, DB::table('users')->where('is_schoolmate', 1)->count());
}
}