|
|
|
|
@ -27,9 +27,12 @@ class CompanyNewsWorkflowTest extends TestCase
|
|
|
|
|
});
|
|
|
|
|
Schema::create('company_qcc_accounts', function (Blueprint $t) {
|
|
|
|
|
$t->id(); $t->unsignedBigInteger('company_id'); $t->string('status'); $t->timestamps();
|
|
|
|
|
$t->dateTime('first_success_at')->nullable(); $t->string('first_success_source')->nullable();
|
|
|
|
|
$t->string('first_success_ref')->nullable(); $t->text('unknown_note')->nullable();
|
|
|
|
|
});
|
|
|
|
|
(require base_path('database/migrations/2026_10_09_100000_create_company_news_tables.php'))->up();
|
|
|
|
|
(require base_path('database/migrations/2026_10_12_100000_add_company_news_category_settings.php'))->up();
|
|
|
|
|
(require base_path('database/migrations/2026_10_12_110000_add_company_news_auto_collection.php'))->up();
|
|
|
|
|
}
|
|
|
|
|
protected function tearDown(): void
|
|
|
|
|
{
|
|
|
|
|
@ -77,7 +80,7 @@ class CompanyNewsWorkflowTest extends TestCase
|
|
|
|
|
$this->assertEquals('<p>人工正文</p>', $news->content_html); $this->assertEquals('discarded', $news->review_status);
|
|
|
|
|
$this->assertFalse($news->is_visible); $this->assertEquals(0, $service->summary($second->id)->inserted);
|
|
|
|
|
$this->assertEquals(1, $service->summary($batch->id)->inserted);
|
|
|
|
|
$this->assertEquals('selected', DB::table('company_qcc_accounts')->value('status'));
|
|
|
|
|
$this->assertEquals('occupied', DB::table('company_qcc_accounts')->value('status'));
|
|
|
|
|
$this->assertEquals(2, DB::table('company_news_logs')->count());
|
|
|
|
|
}
|
|
|
|
|
public function test_failure_does_not_stop_other_companies_or_change_account_states(): void
|
|
|
|
|
@ -182,7 +185,7 @@ class CompanyNewsWorkflowTest extends TestCase
|
|
|
|
|
Carbon::setTestNow('2026-10-12 12:00:00');
|
|
|
|
|
$this->company(1);
|
|
|
|
|
$items = [];
|
|
|
|
|
for ($i = 1; $i <= 8; $i++) $items[] = $this->news('current-'.$i, '2026-01-01 00:00:00');
|
|
|
|
|
for ($i = 1; $i <= 10; $i++) $items[] = $this->news('current-'.$i, '2026-01-01 00:00:00');
|
|
|
|
|
$items[] = $this->news('previous', '2025-12-31 23:59:59');
|
|
|
|
|
$items[] = $this->news('next', '2027-01-01 00:00:00');
|
|
|
|
|
$items[] = $this->news('discarded', '2026-10-12 00:00:00');
|
|
|
|
|
@ -192,7 +195,7 @@ class CompanyNewsWorkflowTest extends TestCase
|
|
|
|
|
$controller = new CompanyNewsController();
|
|
|
|
|
$first = $controller->dashboard(Request::create('/', 'GET', ['page' => 1]))->getData(true);
|
|
|
|
|
$second = $controller->dashboard(Request::create('/', 'GET', ['page' => 2]))->getData(true);
|
|
|
|
|
$this->assertEquals(8, $first['total']); $this->assertCount(6, $first['data']); $this->assertCount(2, $second['data']);
|
|
|
|
|
$this->assertEquals(10, $first['total']); $this->assertCount(8, $first['data']); $this->assertCount(2, $second['data']);
|
|
|
|
|
$this->assertGreaterThan($first['data'][1]['id'], $first['data'][0]['id']);
|
|
|
|
|
$this->assertEmpty(array_intersect(array_column($first['data'], 'id'), array_column($second['data'], 'id')));
|
|
|
|
|
$this->assertEquals(['id', 'title', 'source_url', 'source_published_at'], array_keys($first['data'][0]));
|
|
|
|
|
@ -202,6 +205,88 @@ class CompanyNewsWorkflowTest extends TestCase
|
|
|
|
|
$this->assertEquals(1, $controller->dashboard(Request::create('/'))->getData(true)['total']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_daily_schedule_runs_once_after_start_and_defers_while_active(): void
|
|
|
|
|
{
|
|
|
|
|
Carbon::setTestNow('2026-10-12 21:59:00');
|
|
|
|
|
$this->company(1); $this->provider([$this->response([]), $this->response([])]);
|
|
|
|
|
$service = new NewsCollection();
|
|
|
|
|
$this->assertNull($service->startDue());
|
|
|
|
|
DB::table('company_news_settings')->update(['auto_enabled' => true, 'auto_start_time' => '22:00', 'auto_group_size' => 2]);
|
|
|
|
|
$this->assertNull($service->startDue());
|
|
|
|
|
$manual = $service->start(1);
|
|
|
|
|
Carbon::setTestNow('2026-10-12 22:01:00');
|
|
|
|
|
$this->assertNull($service->startDue());
|
|
|
|
|
$service->runGroup();
|
|
|
|
|
$batch = $service->startDue();
|
|
|
|
|
$this->assertEquals('2026-10-12', $batch->scheduled_date);
|
|
|
|
|
$this->assertEquals(2, $batch->group_size);
|
|
|
|
|
$this->assertNull($service->startDue());
|
|
|
|
|
$service->runGroup();
|
|
|
|
|
$this->assertNull($service->startDue());
|
|
|
|
|
Carbon::setTestNow('2026-10-13 22:00:00');
|
|
|
|
|
$this->assertNotNull($service->startDue());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_group_size_is_snapshotted_and_all_groups_continue_after_failure(): void
|
|
|
|
|
{
|
|
|
|
|
foreach ([1, 2, 3, 4, 5] as $id) $this->company($id);
|
|
|
|
|
DB::table('company_news_settings')->update(['auto_group_size' => 2]);
|
|
|
|
|
$this->provider([new RuntimeException('timeout'), $this->response([]), $this->response([]), $this->response([]), $this->response([])]);
|
|
|
|
|
$service = new NewsCollection(); $batch = $service->start(1);
|
|
|
|
|
DB::table('company_news_settings')->update(['auto_group_size' => 5]);
|
|
|
|
|
$this->assertEquals(2, $service->runGroup());
|
|
|
|
|
$this->assertEquals(1, $service->summary($batch->id)->group_processed);
|
|
|
|
|
$this->assertEquals(2, $service->runGroup());
|
|
|
|
|
$this->assertEquals(1, $service->runGroup());
|
|
|
|
|
$this->assertEquals(0, $service->runGroup());
|
|
|
|
|
$summary = $service->summary($batch->id);
|
|
|
|
|
$this->assertEquals(3, $summary->group_total);
|
|
|
|
|
$this->assertEquals(3, $summary->group_processed);
|
|
|
|
|
$this->assertEquals(5, $summary->processed);
|
|
|
|
|
$this->assertEquals(1, $summary->failed);
|
|
|
|
|
$this->assertEquals('completed', $summary->status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_auto_settings_validation_and_disable_do_not_cancel_active_batch(): void
|
|
|
|
|
{
|
|
|
|
|
$controller = new class extends CompanyNewsController { public function getUserId() { return 1; } };
|
|
|
|
|
$invalid = $controller->saveAutoSettings(Request::create('/', 'POST', ['enabled' => true, 'start_time' => '25:90', 'group_size' => 0]))->getData(true);
|
|
|
|
|
$this->assertEquals(422, $invalid['errcode']);
|
|
|
|
|
$controller->saveAutoSettings(Request::create('/', 'POST', ['enabled' => true, 'start_time' => '22:00', 'group_size' => 10]));
|
|
|
|
|
$this->company(1); $service = new NewsCollection(); $batch = $service->start(1);
|
|
|
|
|
$controller->saveAutoSettings(Request::create('/', 'POST', ['enabled' => false, 'start_time' => '22:00', 'group_size' => 10]));
|
|
|
|
|
$this->assertFalse($controller->autoSettings()->getData(true)['enabled']);
|
|
|
|
|
$this->assertEquals($batch->id, DB::table('company_news_control')->value('active_batch_id'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_business_data_marks_candidates_occupied_even_without_news_and_keeps_first_success(): void
|
|
|
|
|
{
|
|
|
|
|
$this->company(1); $this->company(2); $this->company(3); $this->company(4, 'occupied');
|
|
|
|
|
DB::table('company_qcc_accounts')->where('company_id', 4)->update(['first_success_ref' => 'original']);
|
|
|
|
|
$partners = ['code' => 200, 'data' => ['news' => [], 'partners' => [['StockName' => '股东名称']]]];
|
|
|
|
|
$empty = ['code' => 200, 'data' => ['news' => null, 'listedCompany' => ['detail' => null, 'mainIndicator' => []]]];
|
|
|
|
|
$this->provider([$partners, $empty, ['code' => 500, 'data' => ['partners' => [['StockName' => '股东']]]], $partners]);
|
|
|
|
|
$service = new NewsCollection(); $batch = $service->start(1); while ($service->runOne()) {}
|
|
|
|
|
$this->assertEquals(['occupied', 'selected', 'selected', 'occupied'], DB::table('company_qcc_accounts')->orderBy('company_id')->pluck('status')->all());
|
|
|
|
|
$this->assertStringStartsWith('news-', DB::table('company_qcc_accounts')->where('company_id', 1)->value('first_success_ref'));
|
|
|
|
|
$this->assertEquals('original', DB::table('company_qcc_accounts')->where('company_id', 4)->value('first_success_ref'));
|
|
|
|
|
$this->assertEquals(3, $service->summary($batch->id)->success);
|
|
|
|
|
$this->assertEquals(1, $service->summary($batch->id)->failed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_large_response_imports_every_news_and_deduplicates_on_repeat(): void
|
|
|
|
|
{
|
|
|
|
|
$this->company(1);
|
|
|
|
|
$news = []; for ($i = 0; $i < 901; $i++) $news[] = $this->news('bulk-'.$i);
|
|
|
|
|
$this->provider([$this->response($news), $this->response($news)]);
|
|
|
|
|
$service = new NewsCollection(); $first = $service->start(1); $service->runGroup();
|
|
|
|
|
$this->assertEquals(901, $service->summary($first->id)->inserted);
|
|
|
|
|
$second = $service->start(1); $service->runGroup();
|
|
|
|
|
$this->assertEquals(0, $service->summary($second->id)->inserted);
|
|
|
|
|
$this->assertEquals(901, CompanyNews::count());
|
|
|
|
|
$this->assertEquals('occupied', DB::table('company_qcc_accounts')->value('status'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_empty_batch_finishes_immediately(): void
|
|
|
|
|
{
|
|
|
|
|
$service = new NewsCollection(); $batch = $service->start(1);
|
|
|
|
|
|