1+ <?php
2+
3+ declare (strict_types=1 );
4+
5+ use Illuminate \Http \Client \PendingRequest ;
6+ use Illuminate \Http \Client \Response ;
7+ use Laravel \Boost \Mcp \Tools \SearchDocs ;
8+ use Laravel \Mcp \Server \Tools \ToolResult ;
9+ use Laravel \Roster \Enums \Packages ;
10+ use Laravel \Roster \Package ;
11+ use Laravel \Roster \PackageCollection ;
12+ use Laravel \Roster \Roster ;
13+
14+ test ('it searches documentation successfully ' , function () {
15+ $ packages = new PackageCollection ([
16+ new Package (Packages::LARAVEL , 'laravel/framework ' , '11.0.0 ' ),
17+ new Package (Packages::PEST , 'pestphp/pest ' , '2.0.0 ' ),
18+ ]);
19+
20+ $ roster = Mockery::mock (Roster::class);
21+ $ roster ->shouldReceive ('packages ' )->andReturn ($ packages );
22+
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+ ]
30+ ]);
31+
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 ' ]);
40+
41+ expect ($ result )->toBeInstanceOf (ToolResult::class);
42+
43+ $ 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 ('--- ' );
51+ });
52+
53+ test ('it handles API error response ' , function () {
54+ $ packages = new PackageCollection ([
55+ new Package (Packages::LARAVEL , 'laravel/framework ' , '11.0.0 ' ),
56+ ]);
57+
58+ $ roster = Mockery::mock (Roster::class);
59+ $ roster ->shouldReceive ('packages ' )->andReturn ($ packages );
60+
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 );
72+
73+ $ result = $ tool ->handle (['queries ' => 'authentication ' ]);
74+
75+ expect ($ result )->toBeInstanceOf (ToolResult::class);
76+
77+ $ data = $ result ->toArray ();
78+ expect ($ data ['isError ' ])->toBeTrue ();
79+ expect ($ data ['content ' ][0 ]['text ' ])->toBe ('Failed to search documentation: API Error ' );
80+ });
81+
82+ test ('it filters empty queries ' , function () {
83+ $ packages = new PackageCollection ([]);
84+
85+ $ roster = Mockery::mock (Roster::class);
86+ $ roster ->shouldReceive ('packages ' )->andReturn ($ packages );
87+
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 );
103+
104+ $ result = $ tool ->handle (['queries ' => 'test### ###*### ' ]);
105+
106+ expect ($ result )->toBeInstanceOf (ToolResult::class);
107+
108+ $ data = $ result ->toArray ();
109+ expect ($ data ['isError ' ])->toBeFalse ();
110+ });
111+
112+ test ('it formats package data correctly ' , function () {
113+ $ packages = new PackageCollection ([
114+ new Package (Packages::LARAVEL , 'laravel/framework ' , '11.0.0 ' ),
115+ new Package (Packages::LIVEWIRE , 'livewire/livewire ' , '3.5.1 ' ),
116+ ]);
117+
118+ $ roster = Mockery::mock (Roster::class);
119+ $ roster ->shouldReceive ('packages ' )->andReturn ($ packages );
120+
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 );
139+
140+ $ result = $ tool ->handle (['queries ' => 'test ' ]);
141+
142+ expect ($ result )->toBeInstanceOf (ToolResult::class);
143+ });
144+
145+ test ('it handles empty results ' , function () {
146+ $ packages = new PackageCollection ([]);
147+
148+ $ roster = Mockery::mock (Roster::class);
149+ $ roster ->shouldReceive ('packages ' )->andReturn ($ packages );
150+
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 );
161+
162+ $ result = $ tool ->handle (['queries ' => 'nonexistent ' ]);
163+
164+ expect ($ result )->toBeInstanceOf (ToolResult::class);
165+
166+ $ 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 ('' );
172+ });
173+
174+ test ('it uses custom token_limit when provided ' , function () {
175+ $ packages = new PackageCollection ([]);
176+
177+ $ roster = Mockery::mock (Roster::class);
178+ $ roster ->shouldReceive ('packages ' )->andReturn ($ packages );
179+
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 );
195+
196+ $ result = $ tool ->handle (['queries ' => 'test ' , 'token_limit ' => 5000 ]);
197+
198+ expect ($ result )->toBeInstanceOf (ToolResult::class);
199+ });
200+
201+ test ('it caps token_limit at maximum of 1000000 ' , function () {
202+ $ packages = new PackageCollection ([]);
203+
204+ $ roster = Mockery::mock (Roster::class);
205+ $ roster ->shouldReceive ('packages ' )->andReturn ($ packages );
206+
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 );
222+
223+ $ result = $ tool ->handle (['queries ' => 'test ' , 'token_limit ' => 2000000 ]); // Request 2M but get capped at 1M
224+
225+ expect ($ result )->toBeInstanceOf (ToolResult::class);
226+ });
0 commit comments