Use verifyNoRedirect instead of calling the request handler directly
[Sone.git] / src / test / kotlin / net / pterodactylus / sone / web / pages / SearchPageTest.kt
1 package net.pterodactylus.sone.web.pages
2
3 import com.google.common.base.Optional.absent
4 import net.pterodactylus.sone.data.Album
5 import net.pterodactylus.sone.data.Image
6 import net.pterodactylus.sone.data.Post
7 import net.pterodactylus.sone.data.PostReply
8 import net.pterodactylus.sone.data.Profile
9 import net.pterodactylus.sone.data.Sone
10 import net.pterodactylus.sone.test.asOptional
11 import net.pterodactylus.sone.test.mock
12 import net.pterodactylus.sone.test.whenever
13 import org.hamcrest.MatcherAssert.assertThat
14 import org.hamcrest.Matchers.contains
15 import org.junit.Test
16
17 /**
18  * Unit test for [SearchPage].
19  */
20 class SearchPageTest : WebPageTest() {
21
22         private val page = SearchPage(template, webInterface)
23
24         override fun getPage() = page
25
26         @Test
27         fun `empty query redirects to index page`() {
28                 verifyRedirect("index.html")
29         }
30
31         @Test
32         fun `empty search phrases redirect to index page`() {
33                 addHttpRequestParameter("query", "\"\"")
34                 verifyRedirect("index.html")
35         }
36
37         @Test
38         fun `invalid search phrases redirect to index page`() {
39                 addHttpRequestParameter("query", "\"")
40                 verifyRedirect("index.html")
41         }
42
43         @Test
44         fun `searching for sone link redirects to view sone page`() {
45                 addSone("sone-id", mock<Sone>())
46                 addHttpRequestParameter("query", "sone://sone-id")
47                 verifyRedirect("viewSone.html?sone=sone-id")
48         }
49
50         @Test
51         fun `searching for sone link without prefix redirects to view sone page`() {
52                 addSone("sone-id", mock<Sone>())
53                 addHttpRequestParameter("query", "sone-id")
54                 verifyRedirect("viewSone.html?sone=sone-id")
55         }
56
57         @Test
58         fun `searching for a post link redirects to post page`() {
59                 addPost("post-id", mock<Post>())
60                 addHttpRequestParameter("query", "post://post-id")
61                 verifyRedirect("viewPost.html?post=post-id")
62         }
63
64         @Test
65         fun `searching for a post ID without prefix redirects to post page`() {
66                 addPost("post-id", mock<Post>())
67                 addHttpRequestParameter("query", "post-id")
68                 verifyRedirect("viewPost.html?post=post-id")
69         }
70
71         @Test
72         fun `searching for a reply link redirects to the post page`() {
73                 val postReply = mock<PostReply>().apply { whenever(postId).thenReturn("post-id") }
74                 addPostReply("reply-id", postReply)
75                 addHttpRequestParameter("query", "reply://reply-id")
76                 verifyRedirect("viewPost.html?post=post-id")
77         }
78
79         @Test
80         fun `searching for a reply ID redirects to the post page`() {
81                 val postReply = mock<PostReply>().apply { whenever(postId).thenReturn("post-id") }
82                 addPostReply("reply-id", postReply)
83                 addHttpRequestParameter("query", "reply-id")
84                 verifyRedirect("viewPost.html?post=post-id")
85         }
86
87         @Test
88         fun `searching for an album link redirects to the image browser`() {
89                 addAlbum("album-id", mock<Album>())
90                 addHttpRequestParameter("query", "album://album-id")
91                 verifyRedirect("imageBrowser.html?album=album-id")
92         }
93
94         @Test
95         fun `searching for an album ID redirects to the image browser`() {
96                 addAlbum("album-id", mock<Album>())
97                 addHttpRequestParameter("query", "album-id")
98                 verifyRedirect("imageBrowser.html?album=album-id")
99         }
100
101         @Test
102         fun `searching for an image link redirects to the image browser`() {
103                 addImage("image-id", mock<Image>())
104                 addHttpRequestParameter("query", "image://image-id")
105                 verifyRedirect("imageBrowser.html?image=image-id")
106         }
107
108         @Test
109         fun `searching for an image ID redirects to the image browser`() {
110                 addImage("image-id", mock<Image>())
111                 addHttpRequestParameter("query", "image-id")
112                 verifyRedirect("imageBrowser.html?image=image-id")
113         }
114
115         private fun createReply(text: String, postId: String? = null, sone: Sone? = null) = mock<PostReply>().apply {
116                 whenever(this.text).thenReturn(text)
117                 postId?.run { whenever(this@apply.postId).thenReturn(postId) }
118                 sone?.run { whenever(this@apply.sone).thenReturn(sone) }
119         }
120
121         private fun createPost(id: String, text: String) = mock<Post>().apply {
122                 whenever(this.id).thenReturn(id)
123                 whenever(recipient).thenReturn(absent())
124                 whenever(this.text).thenReturn(text)
125         }
126
127         private fun createSoneWithPost(post: Post, sone: Sone? = null) = sone?.apply {
128                 whenever(posts).thenReturn(listOf(post))
129         } ?: mock<Sone>().apply {
130                 whenever(posts).thenReturn(listOf(post))
131                 whenever(profile).thenReturn(Profile(this))
132         }
133
134         @Test
135         fun `searching for a single word finds the post`() {
136                 val postWithMatch = createPost("post-with-match", "the word here")
137                 val postWithoutMatch = createPost("post-without-match", "no match here")
138                 val soneWithMatch = createSoneWithPost(postWithMatch)
139                 val soneWithoutMatch = createSoneWithPost(postWithoutMatch)
140                 addSone("sone-with-match", soneWithMatch)
141                 addSone("sone-without-match", soneWithoutMatch)
142                 addHttpRequestParameter("query", "word")
143                 verifyNoRedirect {
144                         assertThat(this["postHits"], contains<Post>(postWithMatch))
145                 }
146         }
147
148         @Test
149         fun `searching for a single word locates word in reply`() {
150                 val postWithMatch = createPost("post-with-match", "no match here")
151                 val postWithoutMatch = createPost("post-without-match", "no match here")
152                 val soneWithMatch = createSoneWithPost(postWithMatch)
153                 val soneWithoutMatch = createSoneWithPost(postWithoutMatch)
154                 val replyWithMatch = createReply("the word here", "post-with-match", soneWithMatch)
155                 val replyWithoutMatch = createReply("no match here", "post-without-match", soneWithoutMatch)
156                 addPostReply("reply-with-match", replyWithMatch)
157                 addPostReply("reply-without-match", replyWithoutMatch)
158                 addSone("sone-with-match", soneWithMatch)
159                 addSone("sone-without-match", soneWithoutMatch)
160                 addHttpRequestParameter("query", "word")
161                 verifyNoRedirect {
162                         assertThat(this["postHits"], contains<Post>(postWithMatch))
163                 }
164         }
165
166         private fun createSoneWithPost(idPostfix: String, text: String, recipient: Sone? = null, sender: Sone? = null) =
167                         createPost("post-$idPostfix", text, recipient).apply {
168                                 addSone("sone-$idPostfix", createSoneWithPost(this, sender))
169                         }
170
171         @Test
172         fun `earlier matches score higher than later matches`() {
173                 val postWithEarlyMatch = createSoneWithPost("with-early-match", "optional match")
174                 val postWithLaterMatch = createSoneWithPost("with-later-match", "match that is optional")
175                 addHttpRequestParameter("query", "optional ")
176                 verifyNoRedirect {
177                         assertThat(this["postHits"], contains<Post>(postWithEarlyMatch, postWithLaterMatch))
178                 }
179         }
180
181         @Test
182         fun `searching for required word does not return posts without that word`() {
183                 val postWithRequiredMatch = createSoneWithPost("with-required-match", "required match")
184                 createPost("without-required-match", "not a match")
185                 addHttpRequestParameter("query", "+required ")
186                 verifyNoRedirect {
187                         assertThat(this["postHits"], contains<Post>(postWithRequiredMatch))
188                 }
189         }
190
191         @Test
192         fun `searching for forbidden word does not return posts with that word`() {
193                 createSoneWithPost("with-forbidden-match", "forbidden match")
194                 val postWithoutForbiddenMatch = createSoneWithPost("without-forbidden-match", "not a match")
195                 addHttpRequestParameter("query", "match -forbidden")
196                 verifyNoRedirect {
197                         assertThat(this["postHits"], contains<Post>(postWithoutForbiddenMatch))
198                 }
199         }
200
201         @Test
202         fun `searching for a plus sign searches for optional plus sign`() {
203                 val postWithMatch = createSoneWithPost("with-match", "with + match")
204                 createSoneWithPost("without-match", "without match")
205                 addHttpRequestParameter("query", "+")
206                 verifyNoRedirect {
207                         assertThat(this["postHits"], contains<Post>(postWithMatch))
208                 }
209         }
210
211         @Test
212         fun `searching for a minus sign searches for optional minus sign`() {
213                 val postWithMatch = createSoneWithPost("with-match", "with - match")
214                 createSoneWithPost("without-match", "without match")
215                 addHttpRequestParameter("query", "-")
216                 verifyNoRedirect {
217                         assertThat(this["postHits"], contains<Post>(postWithMatch))
218                 }
219         }
220
221         private fun createPost(id: String, text: String, recipient: Sone?) = mock<Post>().apply {
222                 whenever(this.id).thenReturn(id)
223                 val recipientId = recipient?.id
224                 whenever(this.recipientId).thenReturn(recipientId.asOptional())
225                 whenever(this.recipient).thenReturn(recipient.asOptional())
226                 whenever(this.text).thenReturn(text)
227         }
228
229         private fun createSone(id: String, firstName: String, middleName: String, lastName: String) = mock<Sone>().apply {
230                 whenever(this.id).thenReturn(id)
231                 whenever(this.name).thenReturn(id)
232                 whenever(this.profile).thenReturn(Profile(this).apply {
233                         this.firstName = firstName
234                         this.middleName = middleName
235                         this.lastName = lastName
236                 })
237         }
238
239         @Test
240         fun `searching for a recipient finds the correct post`() {
241                 val recipient = createSone("recipient", "reci", "pi", "ent")
242                 val postWithMatch = createSoneWithPost("with-match", "test", recipient)
243                 createSoneWithPost("without-match", "no match")
244                 addHttpRequestParameter("query", "recipient")
245                 verifyNoRedirect {
246                         assertThat(this["postHits"], contains<Post>(postWithMatch))
247                 }
248         }
249
250         @Test
251         fun `searching for a field value finds the correct sone`() {
252                 val soneWithProfileField = createSone("sone", "s", "o", "ne")
253                 soneWithProfileField.profile.addField("field").value = "value"
254                 createSoneWithPost("with-match", "test", sender = soneWithProfileField)
255                 createSoneWithPost("without-match", "no match")
256                 addHttpRequestParameter("query", "value")
257                 verifyNoRedirect {
258                         assertThat(this["soneHits"], contains(soneWithProfileField))
259                 }
260         }
261
262         @Suppress("UNCHECKED_CAST")
263         private operator fun <T> get(key: String): T? = templateContext[key] as? T
264
265 }