[ [ 'componentId' => 'counter:demo', 'method' => 'increment', 'params' => ['amount' => 5], 'operationId' => 'op-1', ], [ 'componentId' => 'stats:user', 'method' => 'refresh', 'operationId' => 'op-2', ], ], ]; $response = $this->post('/live-component/batch', $requestData); expect($response->status)->toBe(Status::OK); $data = $response->jsonData; expect($data)->toHaveKey('results'); expect($data)->toHaveKey('total_operations'); expect($data)->toHaveKey('success_count'); expect($data)->toHaveKey('failure_count'); expect($data['total_operations'])->toBe(2); expect($data['results'])->toHaveCount(2); }); it('returns error for invalid batch request format', function () { $requestData = [ 'invalid' => 'format', ]; $response = $this->post('/live-component/batch', $requestData); expect($response->status)->toBe(Status::BAD_REQUEST); $data = $response->jsonData; expect($data)->toHaveKey('error'); expect($data)->toHaveKey('error_code'); expect($data['error_code'])->toBe('INVALID_BATCH_REQUEST'); }); it('returns error for empty operations', function () { $requestData = [ 'operations' => [], ]; $response = $this->post('/live-component/batch', $requestData); expect($response->status)->toBe(Status::BAD_REQUEST); expect($response->jsonData['error_code'])->toBe('INVALID_BATCH_REQUEST'); }); it('returns error for too many operations', function () { $operations = []; for ($i = 0; $i < 51; $i++) { $operations[] = [ 'componentId' => "component:$i", 'method' => 'action', ]; } $requestData = ['operations' => $operations]; $response = $this->post('/live-component/batch', $requestData); expect($response->status)->toBe(Status::BAD_REQUEST); expect($response->jsonData['error'])->toContain('cannot exceed 50 operations'); }); it('handles partial failures gracefully', function () { $requestData = [ 'operations' => [ [ 'componentId' => 'counter:demo', 'method' => 'increment', 'operationId' => 'op-1', ], [ 'componentId' => 'invalid:component', 'method' => 'action', 'operationId' => 'op-2', ], [ 'componentId' => 'stats:user', 'method' => 'refresh', 'operationId' => 'op-3', ], ], ]; $response = $this->post('/live-component/batch', $requestData); expect($response->status)->toBe(Status::OK); $data = $response->jsonData; expect($data['total_operations'])->toBe(3); expect($data['success_count'])->toBeGreaterThanOrEqual(1); expect($data['failure_count'])->toBeGreaterThanOrEqual(1); // Check that results preserve operation IDs $operationIds = array_column($data['results'], 'operationId'); expect($operationIds)->toContain('op-1'); expect($operationIds)->toContain('op-2'); expect($operationIds)->toContain('op-3'); }); it('supports fragment-based updates in batch', function () { $requestData = [ 'operations' => [ [ 'componentId' => 'counter:demo', 'method' => 'increment', 'fragments' => ['counter-display'], 'operationId' => 'op-1', ], ], ]; $response = $this->post('/live-component/batch', $requestData); expect($response->status)->toBe(Status::OK); $data = $response->jsonData; $result = $data['results'][0]; if ($result['success']) { // If fragments available, should have fragments instead of html if (isset($result['fragments'])) { expect($result['fragments'])->toBeArray(); expect($result)->not->toHaveKey('html'); } } }); it('preserves state and events in batch response', function () { $requestData = [ 'operations' => [ [ 'componentId' => 'counter:demo', 'method' => 'increment', 'params' => ['amount' => 5], 'operationId' => 'op-1', ], ], ]; $response = $this->post('/live-component/batch', $requestData); expect($response->status)->toBe(Status::OK); $result = $response->jsonData['results'][0]; if ($result['success']) { expect($result)->toHaveKey('state'); expect($result)->toHaveKey('events'); expect($result['state'])->toBeArray(); expect($result['events'])->toBeArray(); } }); it('handles concurrent batch requests independently', function () { $request1 = [ 'operations' => [ ['componentId' => 'counter:demo', 'method' => 'increment', 'operationId' => 'req1-op1'], ], ]; $request2 = [ 'operations' => [ ['componentId' => 'stats:user', 'method' => 'refresh', 'operationId' => 'req2-op1'], ], ]; $response1 = $this->post('/live-component/batch', $request1); $response2 = $this->post('/live-component/batch', $request2); expect($response1->status)->toBe(Status::OK); expect($response2->status)->toBe(Status::OK); expect($response1->jsonData['results'][0]['operationId'])->toBe('req1-op1'); expect($response2->jsonData['results'][0]['operationId'])->toBe('req2-op1'); }); });