runInCoroutine(static function (): int { $record = FailedMessage::query()->create([ 'error_id' => 'err_test_' . uniqid(), 'data_type' => 'order', 'platform' => 'Tmall', 'platform_id' => 1, 'company_id' => 1, 'store_id' => 1, 'error_type' => 'RuntimeException', 'error_message' => 'Test error message for integration test', 'error_code' => 0, 'error_trace' => '#0 test trace line 1\n#1 test trace line 2', 'original_message' => ['data_type' => 'order', 'data' => ['tid' => '12345']], 'retry_count' => 3, 'message_id' => 'msg_test_123', 'failed_at' => '2026-03-13 10:00:00+08', ]); return $record->id; }); self::$testRecordId = $id; return $id; } /** * 清理测试数据 */ protected function tearDown(): void { // 最后一个测试方法执行完毕后清理 parent::tearDown(); } public static function tearDownAfterClass(): void { if (self::$testRecordId !== null) { // co-phpunit 已在协程中,直接使用 runInCoroutine 兼容模式 $id = self::$testRecordId; if (\Swoole\Coroutine::getCid() > 0) { FailedMessage::query()->where('id', $id)->delete(); } else { \Swoole\Coroutine\run(static function () use ($id): void { FailedMessage::query()->where('id', $id)->delete(); }); } self::$testRecordId = null; } parent::tearDownAfterClass(); } // ========== 列表接口 ========== public function test_list_returns_paginated_data(): void { $this->ensureTestData(); $response = $this->get('/api/v1/failed-messages', [], $this->authHeaders()); $response->assertStatus(200); $response->assertJsonPath('code', 0); $response->assertJsonStructure([ 'code', 'message', 'data' => [ 'items', 'total', 'page', 'per_page', ], ]); } public function test_list_respects_per_page(): void { $this->ensureTestData(); $response = $this->get('/api/v1/failed-messages', ['per_page' => 5], $this->authHeaders()); $response->assertStatus(200); $response->assertJsonPath('data.per_page', 5); } public function test_list_excludes_trace_and_original_message(): void { $this->ensureTestData(); $response = $this->get('/api/v1/failed-messages', [], $this->authHeaders()); $response->assertStatus(200); $items = $response->json('data.items'); if (!empty($items)) { $first = $items[0]; $this->assertArrayNotHasKey('error_trace', $first); $this->assertArrayNotHasKey('original_message', $first); $this->assertArrayHasKey('error_type', $first); $this->assertArrayHasKey('error_message', $first); } } // ========== 筛选 ========== public function test_list_filter_by_data_type(): void { $this->ensureTestData(); $response = $this->get('/api/v1/failed-messages', ['data_type' => 'order'], $this->authHeaders()); $response->assertStatus(200); $items = $response->json('data.items'); foreach ($items as $item) { $this->assertSame('order', $item['data_type']); } } public function test_list_filter_by_platform_id(): void { $this->ensureTestData(); $response = $this->get('/api/v1/failed-messages', ['platform_id' => 1], $this->authHeaders()); $response->assertStatus(200); $items = $response->json('data.items'); foreach ($items as $item) { $this->assertSame(1, $item['platform_id']); } } public function test_list_filter_by_failed_at_range(): void { $this->ensureTestData(); $response = $this->get('/api/v1/failed-messages', [ 'failed_at_from' => '2026-03-01', 'failed_at_to' => '2026-03-31', ], $this->authHeaders()); $response->assertStatus(200); $response->assertJsonPath('code', 0); } // ========== 详情接口 ========== public function test_detail_contains_trace_and_original_message(): void { $id = $this->ensureTestData(); $response = $this->get("/api/v1/failed-messages/{$id}", [], $this->authHeaders()); $response->assertStatus(200); $response->assertJsonPath('code', 0); $data = $response->json('data'); $this->assertArrayHasKey('error_trace', $data); $this->assertArrayHasKey('original_message', $data); $this->assertArrayHasKey('error_type', $data); $this->assertArrayHasKey('error_code', $data); $this->assertSame('RuntimeException', $data['error_type']); } public function test_detail_not_found_returns_404(): void { $response = $this->get('/api/v1/failed-messages/999999', [], $this->authHeaders()); $response->assertStatus(404); $this->assertSame(404, $response->json('code')); } // ========== 认证检查 ========== public function test_list_without_token_returns_401(): void { $response = $this->get('/api/v1/failed-messages'); $response->assertStatus(401); } public function test_detail_without_token_returns_401(): void { $response = $this->get('/api/v1/failed-messages/1'); $response->assertStatus(401); } }