]> BookStack Code Mirror - bookstack/blob - tests/Api/SearchApiTest.php
Added include func for search api
[bookstack] / tests / Api / SearchApiTest.php
1 <?php
2
3 namespace Tests\Api;
4
5 use BookStack\Activity\Models\Tag;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Entities\Models\Bookshelf;
8 use BookStack\Entities\Models\Chapter;
9 use BookStack\Entities\Models\Entity;
10 use BookStack\Entities\Models\Page;
11 use Tests\TestCase;
12
13 class SearchApiTest extends TestCase
14 {
15     use TestsApi;
16
17     protected $baseEndpoint = '/api/search';
18
19     public function test_all_endpoint_returns_search_filtered_results_with_query()
20     {
21         $this->actingAsApiEditor();
22         $uniqueTerm = 'MySuperUniqueTermForSearching';
23
24         /** @var Entity $entityClass */
25         foreach ([Page::class, Chapter::class, Book::class, Bookshelf::class] as $entityClass) {
26             /** @var Entity $first */
27             $first = $entityClass::query()->first();
28             $first->update(['name' => $uniqueTerm]);
29             $first->indexForSearch();
30         }
31
32         $resp = $this->getJson($this->baseEndpoint . '?query=' . $uniqueTerm . '&count=5&page=1');
33         $resp->assertJsonCount(4, 'data');
34         $resp->assertJsonFragment(['name' => $uniqueTerm, 'type' => 'book']);
35         $resp->assertJsonFragment(['name' => $uniqueTerm, 'type' => 'chapter']);
36         $resp->assertJsonFragment(['name' => $uniqueTerm, 'type' => 'page']);
37         $resp->assertJsonFragment(['name' => $uniqueTerm, 'type' => 'bookshelf']);
38     }
39
40     public function test_all_endpoint_returns_entity_url()
41     {
42         $page = $this->entities->page();
43         $page->update(['name' => 'name with superuniquevalue within']);
44         $page->indexForSearch();
45
46         $resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?query=superuniquevalue');
47         $resp->assertJsonFragment([
48             'type' => 'page',
49             'url' => $page->getUrl(),
50         ]);
51     }
52
53     public function test_all_endpoint_returns_items_with_preview_html()
54     {
55         $book = $this->entities->book();
56         $book->forceFill(['name' => 'name with superuniquevalue within', 'description' => 'Description with superuniquevalue within'])->save();
57         $book->indexForSearch();
58
59         $resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?query=superuniquevalue');
60         $resp->assertJsonFragment([
61             'type' => 'book',
62             'url' => $book->getUrl(),
63             'preview_html' => [
64                 'name' => 'name with <strong>superuniquevalue</strong> within',
65                 'content' => 'Description with <strong>superuniquevalue</strong> within',
66             ],
67         ]);
68     }
69
70     public function test_all_endpoint_requires_query_parameter()
71     {
72         $resp = $this->actingAsApiEditor()->get($this->baseEndpoint);
73         $resp->assertStatus(422);
74
75         $resp = $this->actingAsApiEditor()->get($this->baseEndpoint . '?query=myqueryvalue');
76         $resp->assertOk();
77     }
78
79     public function test_all_endpoint_includes_book_and_chapter_titles_when_requested()
80     {
81         $this->actingAsApiEditor();
82
83         $book = $this->entities->book();
84         $chapter = $this->entities->chapter();
85         $page = $this->entities->newPage();
86
87         $book->name = 'My Test Book';
88         $book->save();
89
90         $chapter->name = 'My Test Chapter';
91         $chapter->book_id = $book->id;
92         $chapter->save();
93
94         $page->name = 'My Test Page With UniqueSearchTerm';
95         $page->book_id = $book->id;
96         $page->chapter_id = $chapter->id;
97         $page->save();
98
99         $page->indexForSearch();
100
101         // Test without include parameter
102         $resp = $this->getJson($this->baseEndpoint . '?query=UniqueSearchTerm');
103         $resp->assertOk();
104         $resp->assertDontSee('book_title');
105         $resp->assertDontSee('chapter_title');
106
107         // Test with include parameter
108         $resp = $this->getJson($this->baseEndpoint . '?query=UniqueSearchTerm&include=titles');
109         $resp->assertOk();
110         $resp->assertJsonFragment([
111             'name' => 'My Test Page With UniqueSearchTerm',
112             'book_title' => 'My Test Book',
113             'chapter_title' => 'My Test Chapter',
114             'type' => 'page'
115         ]);
116     }
117
118     public function test_all_endpoint_validates_include_parameter()
119     {
120         $this->actingAsApiEditor();
121
122         // Test invalid include value
123         $resp = $this->getJson($this->baseEndpoint . '?query=test&include=invalid');
124         $resp->assertOk();
125         $resp->assertDontSee('book_title');
126
127         // Test SQL injection attempt
128         $resp = $this->getJson($this->baseEndpoint . '?query=test&include=titles;DROP TABLE users');
129         $resp->assertStatus(422);
130
131         // Test multiple includes
132         $resp = $this->getJson($this->baseEndpoint . '?query=test&include=titles,tags');
133         $resp->assertOk();
134     }
135
136     public function test_all_endpoint_includes_tags_when_requested()
137     {
138         $this->actingAsApiEditor();
139
140         // Create a page and give it a unique name for search
141         $page = $this->entities->page();
142         $page->name = 'Page With UniqueSearchTerm';
143         $page->save();
144
145         // Save tags to the page using the existing saveTagsToEntity method
146         $tags = [
147             ['name' => 'SampleTag', 'value' => 'SampleValue']
148         ];
149         app(\BookStack\Activity\TagRepo::class)->saveTagsToEntity($page, $tags);
150
151         // Ensure the page is indexed for search
152         $page->indexForSearch();
153
154         // Test without the "tags" include
155         $resp = $this->getJson($this->baseEndpoint . '?query=UniqueSearchTerm');
156         $resp->assertOk();
157         $resp->assertDontSee('tags');
158
159         // Test with the "tags" include
160         $resp = $this->getJson($this->baseEndpoint . '?query=UniqueSearchTerm&include=tags');
161         $resp->assertOk();
162         
163         // Assert that tags are included in the response
164         $resp->assertJsonFragment([
165             'name' => 'SampleTag',
166             'value' => 'SampleValue',
167         ]);
168
169         // Optionally: check the structure to match the tag order as well
170         $resp->assertJsonStructure([
171             'data' => [
172                 '*' => [
173                     'tags' => [
174                         '*' => [
175                             'name',
176                             'value',
177                             'order',
178                         ],
179                     ],
180                 ],
181             ],
182         ]);
183     }
184
185
186 }