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