1 package net.pterodactylus.sone.web.pages
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.isOnPage
12 import net.pterodactylus.sone.test.mock
13 import net.pterodactylus.sone.test.whenever
14 import net.pterodactylus.sone.utils.Pagination
15 import net.pterodactylus.util.template.TemplateContext
16 import org.hamcrest.MatcherAssert.assertThat
17 import org.hamcrest.Matchers.contains
18 import org.hamcrest.Matchers.equalTo
22 * Unit test for [SearchPage].
24 class SearchPageTest : WebPageTest() {
26 private val page = SearchPage(template, webInterface)
28 override fun getPage() = page
31 fun `page returns correct path`() {
32 assertThat(page.path, equalTo("search.html"))
36 fun `page does not require login`() {
37 assertThat(page.requiresLogin(), equalTo(false))
41 fun `page returns correct title`() {
42 addTranslation("Page.Search.Title", "search page title")
43 assertThat(page.getPageTitle(freenetRequest), equalTo("search page title"))
47 fun `empty query redirects to index page`() {
48 verifyRedirect("index.html")
52 fun `empty search phrases redirect to index page`() {
53 addHttpRequestParameter("query", "\"\"")
54 verifyRedirect("index.html")
58 fun `invalid search phrases redirect to index page`() {
59 addHttpRequestParameter("query", "\"")
60 verifyRedirect("index.html")
64 fun `searching for sone link redirects to view sone page`() {
65 addSone("sone-id", mock<Sone>())
66 addHttpRequestParameter("query", "sone://sone-id")
67 verifyRedirect("viewSone.html?sone=sone-id")
71 fun `searching for sone link without prefix redirects to view sone page`() {
72 addSone("sone-id", mock<Sone>())
73 addHttpRequestParameter("query", "sone-id")
74 verifyRedirect("viewSone.html?sone=sone-id")
78 fun `searching for a post link redirects to post page`() {
79 addPost("post-id", mock<Post>())
80 addHttpRequestParameter("query", "post://post-id")
81 verifyRedirect("viewPost.html?post=post-id")
85 fun `searching for a post ID without prefix redirects to post page`() {
86 addPost("post-id", mock<Post>())
87 addHttpRequestParameter("query", "post-id")
88 verifyRedirect("viewPost.html?post=post-id")
92 fun `searching for a reply link redirects to the post page`() {
93 val postReply = mock<PostReply>().apply { whenever(postId).thenReturn("post-id") }
94 addPostReply("reply-id", postReply)
95 addHttpRequestParameter("query", "reply://reply-id")
96 verifyRedirect("viewPost.html?post=post-id")
100 fun `searching for a reply ID redirects to the post page`() {
101 val postReply = mock<PostReply>().apply { whenever(postId).thenReturn("post-id") }
102 addPostReply("reply-id", postReply)
103 addHttpRequestParameter("query", "reply-id")
104 verifyRedirect("viewPost.html?post=post-id")
108 fun `searching for an album link redirects to the image browser`() {
109 addAlbum("album-id", mock<Album>())
110 addHttpRequestParameter("query", "album://album-id")
111 verifyRedirect("imageBrowser.html?album=album-id")
115 fun `searching for an album ID redirects to the image browser`() {
116 addAlbum("album-id", mock<Album>())
117 addHttpRequestParameter("query", "album-id")
118 verifyRedirect("imageBrowser.html?album=album-id")
122 fun `searching for an image link redirects to the image browser`() {
123 addImage("image-id", mock<Image>())
124 addHttpRequestParameter("query", "image://image-id")
125 verifyRedirect("imageBrowser.html?image=image-id")
129 fun `searching for an image ID redirects to the image browser`() {
130 addImage("image-id", mock<Image>())
131 addHttpRequestParameter("query", "image-id")
132 verifyRedirect("imageBrowser.html?image=image-id")
135 private fun createReply(text: String, postId: String? = null, sone: Sone? = null) = mock<PostReply>().apply {
136 whenever(this.text).thenReturn(text)
137 postId?.run { whenever(this@apply.postId).thenReturn(postId) }
138 sone?.run { whenever(this@apply.sone).thenReturn(sone) }
141 private fun createPost(id: String, text: String) = mock<Post>().apply {
142 whenever(this.id).thenReturn(id)
143 whenever(recipient).thenReturn(absent())
144 whenever(this.text).thenReturn(text)
147 private fun createSoneWithPost(post: Post, sone: Sone? = null) = sone?.apply {
148 whenever(posts).thenReturn(listOf(post))
149 } ?: mock<Sone>().apply {
150 whenever(posts).thenReturn(listOf(post))
151 whenever(profile).thenReturn(Profile(this))
155 fun `searching for a single word finds the post`() {
156 val postWithMatch = createPost("post-with-match", "the word here")
157 val postWithoutMatch = createPost("post-without-match", "no match here")
158 val soneWithMatch = createSoneWithPost(postWithMatch)
159 val soneWithoutMatch = createSoneWithPost(postWithoutMatch)
160 addSone("sone-with-match", soneWithMatch)
161 addSone("sone-without-match", soneWithoutMatch)
162 addHttpRequestParameter("query", "word")
164 assertThat(this["postHits"], contains<Post>(postWithMatch))
169 fun `searching for a single word locates word in reply`() {
170 val postWithMatch = createPost("post-with-match", "no match here")
171 val postWithoutMatch = createPost("post-without-match", "no match here")
172 val soneWithMatch = createSoneWithPost(postWithMatch)
173 val soneWithoutMatch = createSoneWithPost(postWithoutMatch)
174 val replyWithMatch = createReply("the word here", "post-with-match", soneWithMatch)
175 val replyWithoutMatch = createReply("no match here", "post-without-match", soneWithoutMatch)
176 addPostReply("reply-with-match", replyWithMatch)
177 addPostReply("reply-without-match", replyWithoutMatch)
178 addSone("sone-with-match", soneWithMatch)
179 addSone("sone-without-match", soneWithoutMatch)
180 addHttpRequestParameter("query", "word")
182 assertThat(this["postHits"], contains<Post>(postWithMatch))
186 private fun createSoneWithPost(idPostfix: String, text: String, recipient: Sone? = null, sender: Sone? = null) =
187 createPost("post-$idPostfix", text, recipient).apply {
188 addSone("sone-$idPostfix", createSoneWithPost(this, sender))
192 fun `earlier matches score higher than later matches`() {
193 val postWithEarlyMatch = createSoneWithPost("with-early-match", "optional match")
194 val postWithLaterMatch = createSoneWithPost("with-later-match", "match that is optional")
195 addHttpRequestParameter("query", "optional ")
197 assertThat(this["postHits"], contains<Post>(postWithEarlyMatch, postWithLaterMatch))
202 fun `searching for required word does not return posts without that word`() {
203 val postWithRequiredMatch = createSoneWithPost("with-required-match", "required match")
204 createPost("without-required-match", "not a match")
205 addHttpRequestParameter("query", "+required ")
207 assertThat(this["postHits"], contains<Post>(postWithRequiredMatch))
212 fun `searching for forbidden word does not return posts with that word`() {
213 createSoneWithPost("with-forbidden-match", "forbidden match")
214 val postWithoutForbiddenMatch = createSoneWithPost("without-forbidden-match", "not a match")
215 addHttpRequestParameter("query", "match -forbidden")
217 assertThat(this["postHits"], contains<Post>(postWithoutForbiddenMatch))
222 fun `searching for a plus sign searches for optional plus sign`() {
223 val postWithMatch = createSoneWithPost("with-match", "with + match")
224 createSoneWithPost("without-match", "without match")
225 addHttpRequestParameter("query", "+")
227 assertThat(this["postHits"], contains<Post>(postWithMatch))
232 fun `searching for a minus sign searches for optional minus sign`() {
233 val postWithMatch = createSoneWithPost("with-match", "with - match")
234 createSoneWithPost("without-match", "without match")
235 addHttpRequestParameter("query", "-")
237 assertThat(this["postHits"], contains<Post>(postWithMatch))
241 private fun createPost(id: String, text: String, recipient: Sone?) = mock<Post>().apply {
242 whenever(this.id).thenReturn(id)
243 val recipientId = recipient?.id
244 whenever(this.recipientId).thenReturn(recipientId.asOptional())
245 whenever(this.recipient).thenReturn(recipient.asOptional())
246 whenever(this.text).thenReturn(text)
249 private fun createSone(id: String, firstName: String? = null, middleName: String? = null, lastName: String? = null) = mock<Sone>().apply {
250 whenever(this.id).thenReturn(id)
251 whenever(this.name).thenReturn(id)
252 whenever(this.profile).thenReturn(Profile(this).apply {
253 this.firstName = firstName
254 this.middleName = middleName
255 this.lastName = lastName
260 fun `searching for a recipient finds the correct post`() {
261 val recipient = createSone("recipient", "reci", "pi", "ent")
262 val postWithMatch = createSoneWithPost("with-match", "test", recipient)
263 createSoneWithPost("without-match", "no match")
264 addHttpRequestParameter("query", "recipient")
266 assertThat(this["postHits"], contains<Post>(postWithMatch))
271 fun `searching for a field value finds the correct sone`() {
272 val soneWithProfileField = createSone("sone", "s", "o", "ne")
273 soneWithProfileField.profile.addField("field").value = "value"
274 createSoneWithPost("with-match", "test", sender = soneWithProfileField)
275 createSoneWithPost("without-match", "no match")
276 addHttpRequestParameter("query", "value")
278 assertThat(this["soneHits"], contains(soneWithProfileField))
283 fun `sone hits are paginated correctly`() {
284 core.preferences.postsPerPage = 2
285 val sones = listOf(createSone("1Sone"), createSone("Other1"), createSone("22Sone"), createSone("333Sone"), createSone("Other2"))
286 .onEach { addSone(it.id, it) }
287 addHttpRequestParameter("query", "sone")
289 assertThat(this["sonePagination"], isOnPage(0).hasPages(2))
290 assertThat(this["soneHits"], contains(sones[0], sones[2]))
295 fun `sone hits page 2 is shown correctly`() {
296 core.preferences.postsPerPage = 2
297 val sones = listOf(createSone("1Sone"), createSone("Other1"), createSone("22Sone"), createSone("333Sone"), createSone("Other2"))
298 .onEach { addSone(it.id, it) }
299 addHttpRequestParameter("query", "sone")
300 addHttpRequestParameter("sonePage", "1")
302 assertThat(this["sonePagination"], isOnPage(1).hasPages(2))
303 assertThat(this["soneHits"], contains(sones[3]))
308 fun `post hits are paginated correctly`() {
309 core.preferences.postsPerPage = 2
310 val sones = listOf(createSoneWithPost("match1", "1Sone"), createSoneWithPost("no-match1", "Other1"), createSoneWithPost("match2", "22Sone"), createSoneWithPost("match3", "333Sone"), createSoneWithPost("no-match2", "Other2"))
311 addHttpRequestParameter("query", "sone")
313 assertThat(this["postPagination"], isOnPage(0).hasPages(2))
314 assertThat(this["postHits"], contains(sones[0], sones[2]))
319 fun `post hits page 2 is shown correctly`() {
320 core.preferences.postsPerPage = 2
321 val sones = listOf(createSoneWithPost("match1", "1Sone"), createSoneWithPost("no-match1", "Other1"), createSoneWithPost("match2", "22Sone"), createSoneWithPost("match3", "333Sone"), createSoneWithPost("no-match2", "Other2"))
322 addHttpRequestParameter("query", "sone")
323 addHttpRequestParameter("postPage", "1")
325 assertThat(this["postPagination"], isOnPage(1).hasPages(2))
326 assertThat(this["postHits"], contains(sones[3]))
330 @Suppress("UNCHECKED_CAST")
331 private operator fun <T> get(key: String): T? = templateContext[key] as? T