|
2 | 2 |
|
3 | 3 | declare(strict_types=1); |
4 | 4 |
|
5 | | -use Illuminate\Http\Client\PendingRequest; |
6 | | -use Illuminate\Http\Client\Response; |
| 5 | +use Illuminate\Support\Facades\Http; |
7 | 6 | use Laravel\Boost\Mcp\Tools\SearchDocs; |
8 | 7 | use Laravel\Mcp\Server\Tools\ToolResult; |
9 | 8 | use Laravel\Roster\Enums\Packages; |
|
20 | 19 | $roster = Mockery::mock(Roster::class); |
21 | 20 | $roster->shouldReceive('packages')->andReturn($packages); |
22 | 21 |
|
23 | | - $mockResponse = Mockery::mock(Response::class); |
24 | | - $mockResponse->shouldReceive('successful')->andReturn(true); |
25 | | - $mockResponse->shouldReceive('json')->andReturn([ |
26 | | - 'results' => [ |
27 | | - ['content' => 'Laravel documentation content'], |
28 | | - ['content' => 'Pest documentation content'], |
29 | | - ], |
| 22 | + Http::fake([ |
| 23 | + 'https://boost.laravel.com/api/docs' => Http::response('Documentation search results', 200), |
30 | 24 | ]); |
31 | 25 |
|
32 | | - $mockClient = Mockery::mock(PendingRequest::class); |
33 | | - $mockClient->shouldReceive('asJson')->andReturnSelf(); |
34 | | - $mockClient->shouldReceive('post')->andReturn($mockResponse); |
35 | | - |
36 | | - $tool = Mockery::mock(SearchDocs::class, [$roster])->makePartial(); |
37 | | - $tool->shouldReceive('client')->andReturn($mockClient); |
38 | | - |
39 | | - $result = $tool->handle(['queries' => 'authentication, testing']); |
| 26 | + $tool = new SearchDocs($roster); |
| 27 | + $result = $tool->handle(['queries' => 'authentication###testing']); |
40 | 28 |
|
41 | 29 | expect($result)->toBeInstanceOf(ToolResult::class); |
42 | 30 |
|
43 | 31 | $data = $result->toArray(); |
44 | | - expect($data['isError'])->toBeFalse(); |
45 | | - |
46 | | - $content = json_decode($data['content'][0]['text'], true); |
47 | | - expect($content['knowledge_count'])->toBe(2); |
48 | | - expect($content['knowledge'])->toContain('Laravel documentation content'); |
49 | | - expect($content['knowledge'])->toContain('Pest documentation content'); |
50 | | - expect($content['knowledge'])->toContain('---'); |
| 32 | + expect($data['isError'])->toBeFalse() |
| 33 | + ->and($data['content'][0]['text'])->toBe('Documentation search results'); |
| 34 | + |
| 35 | + Http::assertSent(function ($request) { |
| 36 | + return $request->url() === 'https://boost.laravel.com/api/docs' && |
| 37 | + $request->data()['queries'] === ['authentication', 'testing'] && |
| 38 | + $request->data()['packages'] === [ |
| 39 | + ['name' => 'laravel/framework', 'version' => '11.x'], |
| 40 | + ['name' => 'pestphp/pest', 'version' => '2.x'], |
| 41 | + ] && |
| 42 | + $request->data()['token_limit'] === 10000 && |
| 43 | + $request->data()['format'] === 'markdown'; |
| 44 | + }); |
51 | 45 | }); |
52 | 46 |
|
53 | 47 | test('it handles API error response', function () { |
|
58 | 52 | $roster = Mockery::mock(Roster::class); |
59 | 53 | $roster->shouldReceive('packages')->andReturn($packages); |
60 | 54 |
|
61 | | - $mockResponse = Mockery::mock(Response::class); |
62 | | - $mockResponse->shouldReceive('successful')->andReturn(false); |
63 | | - $mockResponse->shouldReceive('status')->andReturn(500); |
64 | | - $mockResponse->shouldReceive('body')->andReturn('API Error'); |
65 | | - |
66 | | - $mockClient = Mockery::mock(PendingRequest::class); |
67 | | - $mockClient->shouldReceive('asJson')->andReturnSelf(); |
68 | | - $mockClient->shouldReceive('post')->andReturn($mockResponse); |
69 | | - |
70 | | - $tool = Mockery::mock(SearchDocs::class, [$roster])->makePartial(); |
71 | | - $tool->shouldReceive('client')->andReturn($mockClient); |
| 55 | + Http::fake([ |
| 56 | + 'https://boost.laravel.com/api/docs' => Http::response('API Error', 500), |
| 57 | + ]); |
72 | 58 |
|
| 59 | + $tool = new SearchDocs($roster); |
73 | 60 | $result = $tool->handle(['queries' => 'authentication']); |
74 | 61 |
|
75 | 62 | expect($result)->toBeInstanceOf(ToolResult::class); |
76 | 63 |
|
77 | 64 | $data = $result->toArray(); |
78 | | - expect($data['isError'])->toBeTrue(); |
79 | | - expect($data['content'][0]['text'])->toBe('Failed to search documentation: API Error'); |
| 65 | + expect($data['isError'])->toBeTrue() |
| 66 | + ->and($data['content'][0]['text'])->toBe('Failed to search documentation: API Error'); |
80 | 67 | }); |
81 | 68 |
|
82 | 69 | test('it filters empty queries', function () { |
|
85 | 72 | $roster = Mockery::mock(Roster::class); |
86 | 73 | $roster->shouldReceive('packages')->andReturn($packages); |
87 | 74 |
|
88 | | - $mockResponse = Mockery::mock(Response::class); |
89 | | - $mockResponse->shouldReceive('successful')->andReturn(true); |
90 | | - $mockResponse->shouldReceive('json')->andReturn(['results' => []]); |
91 | | - |
92 | | - $mockClient = Mockery::mock(PendingRequest::class); |
93 | | - $mockClient->shouldReceive('asJson')->andReturnSelf(); |
94 | | - $mockClient->shouldReceive('post')->withArgs(function ($url, $payload) { |
95 | | - return $url === 'https://boost.laravel.com/api/docs' && |
96 | | - $payload['queries'] === ['test'] && |
97 | | - empty($payload['packages']) && |
98 | | - $payload['token_limit'] === 10000; |
99 | | - })->andReturn($mockResponse); |
100 | | - |
101 | | - $tool = Mockery::mock(SearchDocs::class, [$roster])->makePartial(); |
102 | | - $tool->shouldReceive('client')->andReturn($mockClient); |
| 75 | + Http::fake([ |
| 76 | + 'https://boost.laravel.com/api/docs' => Http::response('Empty results', 200), |
| 77 | + ]); |
103 | 78 |
|
| 79 | + $tool = new SearchDocs($roster); |
104 | 80 | $result = $tool->handle(['queries' => 'test### ###*### ']); |
105 | 81 |
|
106 | 82 | expect($result)->toBeInstanceOf(ToolResult::class); |
107 | 83 |
|
108 | 84 | $data = $result->toArray(); |
109 | 85 | expect($data['isError'])->toBeFalse(); |
| 86 | + |
| 87 | + Http::assertSent(function ($request) { |
| 88 | + return $request->url() === 'https://boost.laravel.com/api/docs' && |
| 89 | + $request->data()['queries'] === ['test'] && |
| 90 | + empty($request->data()['packages']) && |
| 91 | + $request->data()['token_limit'] === 10000; |
| 92 | + }); |
110 | 93 | }); |
111 | 94 |
|
112 | 95 | test('it formats package data correctly', function () { |
|
118 | 101 | $roster = Mockery::mock(Roster::class); |
119 | 102 | $roster->shouldReceive('packages')->andReturn($packages); |
120 | 103 |
|
121 | | - $mockResponse = Mockery::mock(Response::class); |
122 | | - $mockResponse->shouldReceive('successful')->andReturn(true); |
123 | | - $mockResponse->shouldReceive('json')->andReturn(['results' => []]); |
124 | | - |
125 | | - $mockClient = Mockery::mock(PendingRequest::class); |
126 | | - $mockClient->shouldReceive('asJson')->andReturnSelf(); |
127 | | - $mockClient->shouldReceive('post')->with( |
128 | | - 'https://boost.laravel.com/api/docs', |
129 | | - Mockery::on(function ($payload) { |
130 | | - return $payload['packages'] === [ |
131 | | - ['name' => 'laravel/framework', 'version' => '11.x'], |
132 | | - ['name' => 'livewire/livewire', 'version' => '3.x'], |
133 | | - ] && $payload['token_limit'] === 10000; |
134 | | - }) |
135 | | - )->andReturn($mockResponse); |
136 | | - |
137 | | - $tool = Mockery::mock(SearchDocs::class, [$roster])->makePartial(); |
138 | | - $tool->shouldReceive('client')->andReturn($mockClient); |
| 104 | + Http::fake([ |
| 105 | + 'https://boost.laravel.com/api/docs' => Http::response('Package data results', 200), |
| 106 | + ]); |
139 | 107 |
|
| 108 | + $tool = new SearchDocs($roster); |
140 | 109 | $result = $tool->handle(['queries' => 'test']); |
141 | 110 |
|
142 | 111 | expect($result)->toBeInstanceOf(ToolResult::class); |
| 112 | + |
| 113 | + Http::assertSent(function ($request) { |
| 114 | + return $request->data()['packages'] === [ |
| 115 | + ['name' => 'laravel/framework', 'version' => '11.x'], |
| 116 | + ['name' => 'livewire/livewire', 'version' => '3.x'], |
| 117 | + ] && $request->data()['token_limit'] === 10000; |
| 118 | + }); |
143 | 119 | }); |
144 | 120 |
|
145 | 121 | test('it handles empty results', function () { |
|
148 | 124 | $roster = Mockery::mock(Roster::class); |
149 | 125 | $roster->shouldReceive('packages')->andReturn($packages); |
150 | 126 |
|
151 | | - $mockResponse = Mockery::mock(Response::class); |
152 | | - $mockResponse->shouldReceive('successful')->andReturn(true); |
153 | | - $mockResponse->shouldReceive('json')->andReturn(['results' => []]); |
154 | | - |
155 | | - $mockClient = Mockery::mock(PendingRequest::class); |
156 | | - $mockClient->shouldReceive('asJson')->andReturnSelf(); |
157 | | - $mockClient->shouldReceive('post')->andReturn($mockResponse); |
158 | | - |
159 | | - $tool = Mockery::mock(SearchDocs::class, [$roster])->makePartial(); |
160 | | - $tool->shouldReceive('client')->andReturn($mockClient); |
| 127 | + Http::fake([ |
| 128 | + 'https://boost.laravel.com/api/docs' => Http::response('Empty response', 200), |
| 129 | + ]); |
161 | 130 |
|
| 131 | + $tool = new SearchDocs($roster); |
162 | 132 | $result = $tool->handle(['queries' => 'nonexistent']); |
163 | 133 |
|
164 | 134 | expect($result)->toBeInstanceOf(ToolResult::class); |
165 | 135 |
|
166 | 136 | $data = $result->toArray(); |
167 | | - expect($data['isError'])->toBeFalse(); |
168 | | - |
169 | | - $content = json_decode($data['content'][0]['text'], true); |
170 | | - expect($content['knowledge_count'])->toBe(0); |
171 | | - expect($content['knowledge'])->toBe(''); |
| 137 | + expect($data['isError'])->toBeFalse() |
| 138 | + ->and($data['content'][0]['text'])->toBe('Empty response'); |
172 | 139 | }); |
173 | 140 |
|
174 | 141 | test('it uses custom token_limit when provided', function () { |
|
177 | 144 | $roster = Mockery::mock(Roster::class); |
178 | 145 | $roster->shouldReceive('packages')->andReturn($packages); |
179 | 146 |
|
180 | | - $mockResponse = Mockery::mock(Response::class); |
181 | | - $mockResponse->shouldReceive('successful')->andReturn(true); |
182 | | - $mockResponse->shouldReceive('json')->andReturn(['results' => []]); |
183 | | - |
184 | | - $mockClient = Mockery::mock(PendingRequest::class); |
185 | | - $mockClient->shouldReceive('asJson')->andReturnSelf(); |
186 | | - $mockClient->shouldReceive('post')->with( |
187 | | - 'https://boost.laravel.com/api/docs', |
188 | | - Mockery::on(function ($payload) { |
189 | | - return $payload['token_limit'] === 5000; |
190 | | - }) |
191 | | - )->andReturn($mockResponse); |
192 | | - |
193 | | - $tool = Mockery::mock(SearchDocs::class, [$roster])->makePartial(); |
194 | | - $tool->shouldReceive('client')->andReturn($mockClient); |
| 147 | + Http::fake([ |
| 148 | + 'https://boost.laravel.com/api/docs' => Http::response('Custom token limit results', 200), |
| 149 | + ]); |
195 | 150 |
|
| 151 | + $tool = new SearchDocs($roster); |
196 | 152 | $result = $tool->handle(['queries' => 'test', 'token_limit' => 5000]); |
197 | 153 |
|
198 | 154 | expect($result)->toBeInstanceOf(ToolResult::class); |
| 155 | + |
| 156 | + Http::assertSent(function ($request) { |
| 157 | + return $request->data()['token_limit'] === 5000; |
| 158 | + }); |
199 | 159 | }); |
200 | 160 |
|
201 | 161 | test('it caps token_limit at maximum of 1000000', function () { |
|
204 | 164 | $roster = Mockery::mock(Roster::class); |
205 | 165 | $roster->shouldReceive('packages')->andReturn($packages); |
206 | 166 |
|
207 | | - $mockResponse = Mockery::mock(Response::class); |
208 | | - $mockResponse->shouldReceive('successful')->andReturn(true); |
209 | | - $mockResponse->shouldReceive('json')->andReturn(['results' => []]); |
210 | | - |
211 | | - $mockClient = Mockery::mock(PendingRequest::class); |
212 | | - $mockClient->shouldReceive('asJson')->andReturnSelf(); |
213 | | - $mockClient->shouldReceive('post')->with( |
214 | | - 'https://boost.laravel.com/api/docs', |
215 | | - Mockery::on(function ($payload) { |
216 | | - return $payload['token_limit'] === 1000000; // Should be capped at 1M |
217 | | - }) |
218 | | - )->andReturn($mockResponse); |
219 | | - |
220 | | - $tool = Mockery::mock(SearchDocs::class, [$roster])->makePartial(); |
221 | | - $tool->shouldReceive('client')->andReturn($mockClient); |
| 167 | + Http::fake([ |
| 168 | + 'https://boost.laravel.com/api/docs' => Http::response('Capped token limit results', 200), |
| 169 | + ]); |
222 | 170 |
|
223 | | - $result = $tool->handle(['queries' => 'test', 'token_limit' => 2000000]); // Request 2M but get capped at 1M |
| 171 | + $tool = new SearchDocs($roster); |
| 172 | + $result = $tool->handle(['queries' => 'test', 'token_limit' => 2000000]); |
224 | 173 |
|
225 | 174 | expect($result)->toBeInstanceOf(ToolResult::class); |
| 175 | + |
| 176 | + Http::assertSent(function ($request) { |
| 177 | + return $request->data()['token_limit'] === 1000000; |
| 178 | + }); |
226 | 179 | }); |
0 commit comments