From: David ‘Bombe’ Roden Date: Sat, 8 Apr 2017 13:12:20 +0000 (+0200) Subject: Move web pages to their own package X-Git-Tag: 0.9.7^2~260 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=de7568a82eb4150bf6d2b0553841b7b69f84c968;p=Sone.git Move web pages to their own package --- diff --git a/src/main/java/net/pterodactylus/sone/main/DebugLoaders.java b/src/main/java/net/pterodactylus/sone/main/DebugLoaders.java index 11755e0..d0168ae 100644 --- a/src/main/java/net/pterodactylus/sone/main/DebugLoaders.java +++ b/src/main/java/net/pterodactylus/sone/main/DebugLoaders.java @@ -3,7 +3,7 @@ package net.pterodactylus.sone.main; import java.io.File; import net.pterodactylus.sone.template.FilesystemTemplate; -import net.pterodactylus.sone.web.ReloadingPage; +import net.pterodactylus.sone.web.pages.ReloadingPage; import net.pterodactylus.util.template.FilesystemTemplateProvider; import net.pterodactylus.util.template.Template; import net.pterodactylus.util.template.TemplateProvider; diff --git a/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.kt b/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.kt deleted file mode 100644 index e91ad37..0000000 --- a/src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.kt +++ /dev/null @@ -1,41 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty -import net.pterodactylus.sone.text.TextFilter -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user create a new album. - */ -class CreateAlbumPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("createAlbum.html", template, "Page.CreateAlbum.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - if (request.isPOST) { - val name = request.httpRequest.getPartAsStringFailsafe("name", 64).trim() - if (name.isEmpty()) { - templateContext["nameMissing"] = true - return - } - val description = request.httpRequest.getPartAsStringFailsafe("description", 256).trim() - val currentSone = webInterface.getCurrentSoneCreatingSession(request.toadletContext) - val parentId = request.httpRequest.getPartAsStringFailsafe("parent", 36) - val parent = if (parentId == "") currentSone.rootAlbum else webInterface.core.getAlbum(parentId) - val album = webInterface.core.createAlbum(currentSone, parent) - try { - album.modify().apply { - setTitle(name) - setDescription(TextFilter.filter(request.httpRequest.getHeader("Host"), description)) - }.update() - } catch (e: AlbumTitleMustNotBeEmpty) { - throw RedirectException("emptyAlbumTitle.html") - } - webInterface.core.touchConfiguration() - throw RedirectException("imageBrowser.html?album=${album.id}") - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/CreateReplyPage.kt b/src/main/java/net/pterodactylus/sone/web/CreateReplyPage.kt deleted file mode 100644 index 43fbeaf..0000000 --- a/src/main/java/net/pterodactylus/sone/web/CreateReplyPage.kt +++ /dev/null @@ -1,31 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.text.TextFilter -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * This page lets the user post a reply to a post. - */ -class CreateReplyPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("createReply.html", template, "Page.CreateReply.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - val postId = request.httpRequest.getPartAsStringFailsafe("post", 36).apply { templateContext["postId"] = this } - val text = request.httpRequest.getPartAsStringFailsafe("text", 65536).trim().apply { templateContext["text"] = this } - val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256).apply { templateContext["returnPage"] = this } - if (request.isPOST) { - if (text == "") { - templateContext["errorTextEmpty"] = true - return - } - val post = webInterface.core.getPost(postId).orNull() ?: throw RedirectException("noPermission.html") - val sender = webInterface.core.getLocalSone(request.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: getCurrentSone(request.toadletContext) - webInterface.core.createReply(sender, post, TextFilter.filter(request.httpRequest.getHeader("Host"), text)) - throw RedirectException(returnPage) - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/EditImagePage.java b/src/main/java/net/pterodactylus/sone/web/EditImagePage.java deleted file mode 100644 index 419bc1d..0000000 --- a/src/main/java/net/pterodactylus/sone/web/EditImagePage.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Sone - EditImagePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import net.pterodactylus.sone.data.Image; -import net.pterodactylus.sone.text.TextFilter; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * Page that lets the user edit title and description of an {@link Image}. - * - * @author David ‘Bombe’ Roden - */ -public class EditImagePage extends SoneTemplatePage { - - /** - * Creates a new “edit image” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public EditImagePage(Template template, WebInterface webInterface) { - super("editImage.html", template, "Page.EditImage.Title", webInterface, true); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - String imageId = request.getHttpRequest().getPartAsStringFailsafe("image", 36); - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - Image image = webInterface.getCore().getImage(imageId, false); - if (image == null) { - throw new RedirectException("invalid.html"); - } - if (!image.getSone().isLocal()) { - throw new RedirectException("noPermission.html"); - } - if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveLeft", 4))) { - image.getAlbum().moveImageUp(image); - } else if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveRight", 4))) { - image.getAlbum().moveImageDown(image); - } else { - String title = request.getHttpRequest().getPartAsStringFailsafe("title", 100).trim(); - String description = request.getHttpRequest().getPartAsStringFailsafe("description", 1024).trim(); - if (title.length() == 0) { - throw new RedirectException("emptyImageTitle.html"); - } - image.modify().setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).update(); - } - webInterface.getCore().touchConfiguration(); - throw new RedirectException(returnPage); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java b/src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java deleted file mode 100644 index bbc6d6b..0000000 --- a/src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Sone - EditProfileFieldPage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import net.pterodactylus.sone.data.Profile; -import net.pterodactylus.sone.data.Profile.Field; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * Page that lets the user edit the name of a profile field. - * - * @author David ‘Bombe’ Roden - */ -public class EditProfileFieldPage extends SoneTemplatePage { - - /** - * Creates a new “edit profile field” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public EditProfileFieldPage(Template template, WebInterface webInterface) { - super("editProfileField.html", template, "Page.EditProfileField.Title", webInterface, true); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - Sone currentSone = getCurrentSone(request.getToadletContext()); - Profile profile = currentSone.getProfile(); - - /* get parameters from request. */ - String fieldId = request.getHttpRequest().getParam("field"); - Field field = profile.getFieldById(fieldId); - if (field == null) { - throw new RedirectException("invalid.html"); - } - - /* process the POST request. */ - if (request.getMethod() == Method.POST) { - if (request.getHttpRequest().getPartAsStringFailsafe("cancel", 4).equals("true")) { - throw new RedirectException("editProfile.html#profile-fields"); - } - fieldId = request.getHttpRequest().getPartAsStringFailsafe("field", 36); - field = profile.getFieldById(fieldId); - if (field == null) { - throw new RedirectException("invalid.html"); - } - String name = request.getHttpRequest().getPartAsStringFailsafe("name", 256); - Field existingField = profile.getFieldByName(name); - if ((existingField != null) && (!field.equals(existingField))) { - templateContext.set("duplicateFieldName", true); - } else { - if (!name.equals(field.getName())) { - field.setName(name); - currentSone.setProfile(profile); - } - throw new RedirectException("editProfile.html#profile-fields"); - } - } - - /* store current values in template. */ - templateContext.set("field", field); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java b/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java deleted file mode 100644 index cffdeb9..0000000 --- a/src/main/java/net/pterodactylus/sone/web/EditProfilePage.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Sone - EditProfilePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static net.pterodactylus.sone.text.TextFilter.filter; -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.util.List; - -import net.pterodactylus.sone.data.Profile; -import net.pterodactylus.sone.data.Profile.DuplicateField; -import net.pterodactylus.sone.data.Profile.Field; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; -import freenet.clients.http.ToadletContext; - -/** - * This page lets the user edit her profile. - * - * @author David ‘Bombe’ Roden - */ -public class EditProfilePage extends SoneTemplatePage { - - /** - * Creates a new “edit profile” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public EditProfilePage(Template template, WebInterface webInterface) { - super("editProfile.html", template, "Page.EditProfile.Title", webInterface, true); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - ToadletContext toadletContenxt = request.getToadletContext(); - Sone currentSone = getCurrentSone(toadletContenxt); - Profile profile = currentSone.getProfile(); - String firstName = profile.getFirstName(); - String middleName = profile.getMiddleName(); - String lastName = profile.getLastName(); - Integer birthDay = profile.getBirthDay(); - Integer birthMonth = profile.getBirthMonth(); - Integer birthYear = profile.getBirthYear(); - String avatarId = profile.getAvatar(); - List fields = profile.getFields(); - if (request.getMethod() == Method.POST) { - if (request.getHttpRequest().getPartAsStringFailsafe("save-profile", 4).equals("true")) { - firstName = request.getHttpRequest().getPartAsStringFailsafe("first-name", 256).trim(); - middleName = request.getHttpRequest().getPartAsStringFailsafe("middle-name", 256).trim(); - lastName = request.getHttpRequest().getPartAsStringFailsafe("last-name", 256).trim(); - birthDay = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim(), null); - birthMonth = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim(), null); - birthYear = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim(), null); - avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatarId", 36); - profile.setFirstName(firstName.length() > 0 ? firstName : null); - profile.setMiddleName(middleName.length() > 0 ? middleName : null); - profile.setLastName(lastName.length() > 0 ? lastName : null); - profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear); - profile.setAvatar(webInterface.getCore().getImage(avatarId, false)); - for (Field field : fields) { - String value = request.getHttpRequest().getPartAsStringFailsafe("field-" + field.getId(), 400); - String filteredValue = filter(request.getHttpRequest().getHeader("Host"), value); - field.setValue(filteredValue); - } - currentSone.setProfile(profile); - webInterface.getCore().touchConfiguration(); - throw new RedirectException("editProfile.html"); - } else if (request.getHttpRequest().getPartAsStringFailsafe("add-field", 4).equals("true")) { - String fieldName = request.getHttpRequest().getPartAsStringFailsafe("field-name", 256).trim(); - try { - profile.addField(fieldName); - currentSone.setProfile(profile); - webInterface.getCore().touchConfiguration(); - throw new RedirectException("editProfile.html#profile-fields"); - } catch (DuplicateField df1) { - templateContext.set("fieldName", fieldName); - templateContext.set("duplicateFieldName", true); - } - } else { - String id = getFieldId(request, "delete-field-"); - if (id != null) { - throw new RedirectException("deleteProfileField.html?field=" + id); - } - id = getFieldId(request, "move-up-field-"); - if (id != null) { - Field field = profile.getFieldById(id); - if (field == null) { - throw new RedirectException("invalid.html"); - } - profile.moveFieldUp(field); - currentSone.setProfile(profile); - throw new RedirectException("editProfile.html#profile-fields"); - } - id = getFieldId(request, "move-down-field-"); - if (id != null) { - Field field = profile.getFieldById(id); - if (field == null) { - throw new RedirectException("invalid.html"); - } - profile.moveFieldDown(field); - currentSone.setProfile(profile); - throw new RedirectException("editProfile.html#profile-fields"); - } - id = getFieldId(request, "edit-field-"); - if (id != null) { - throw new RedirectException("editProfileField.html?field=" + id); - } - } - } - templateContext.set("firstName", firstName); - templateContext.set("middleName", middleName); - templateContext.set("lastName", lastName); - templateContext.set("birthDay", birthDay); - templateContext.set("birthMonth", birthMonth); - templateContext.set("birthYear", birthYear); - templateContext.set("avatarId", avatarId); - templateContext.set("fields", fields); - } - - // - // PRIVATE METHODS - // - - /** - * Searches for a part whose names starts with the given {@code String} and - * extracts the ID from the located name. - * - * @param request - * The request to get the parts from - * @param partNameStart - * The start of the name of the requested part - * @return The parsed ID, or {@code null} if there was no part matching the - * given string - */ - private static String getFieldId(FreenetRequest request, String partNameStart) { - for (String partName : request.getHttpRequest().getParts()) { - if (partName.startsWith(partNameStart)) { - return partName.substring(partNameStart.length()); - } - } - return null; - } -} diff --git a/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java deleted file mode 100644 index 0652a52..0000000 --- a/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Sone - FollowSonePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import com.google.common.base.Optional; - -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * This page lets the user follow another Sone. - * - * @author David ‘Bombe’ Roden - */ -public class FollowSonePage extends SoneTemplatePage { - - /** - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public FollowSonePage(Template template, WebInterface webInterface) { - super("followSone.html", template, "Page.FollowSone.Title", webInterface, true); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - Sone currentSone = getCurrentSone(request.getToadletContext()); - String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 1200); - for (String soneId : soneIds.split("[ ,]+")) { - Optional sone = webInterface.getCore().getSone(soneId); - if (sone.isPresent()) { - webInterface.getCore().followSone(currentSone, soneId); - webInterface.getCore().markSoneKnown(sone.get()); - } - } - throw new RedirectException(returnPage); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/GetImagePage.java b/src/main/java/net/pterodactylus/sone/web/GetImagePage.java deleted file mode 100644 index ec38d0a..0000000 --- a/src/main/java/net/pterodactylus/sone/web/GetImagePage.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Sone - GetImagePage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import java.io.IOException; -import java.net.URI; - -import net.pterodactylus.sone.data.TemporaryImage; -import net.pterodactylus.sone.web.page.FreenetPage; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.web.Response; - -/** - * Page that delivers a {@link TemporaryImage} to the browser. - * - * @author David ‘Bombe’ Roden - */ -public class GetImagePage implements FreenetPage { - - /** The Sone web interface. */ - private final WebInterface webInterface; - - /** - * Creates a new “get image” page. - * - * @param webInterface - * The Sone web interface - */ - public GetImagePage(WebInterface webInterface) { - this.webInterface = webInterface; - } - - /** - * {@inheritDoc} - */ - @Override - public String getPath() { - return "getImage.html"; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isPrefixPage() { - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public Response handleRequest(FreenetRequest request, Response response) throws IOException { - String imageId = request.getHttpRequest().getParam("image"); - TemporaryImage temporaryImage = webInterface.getCore().getTemporaryImage(imageId); - if (temporaryImage == null) { - return response.setStatusCode(404).setStatusText("Not found.").setContentType("text/html; charset=utf-8"); - } - String contentType= temporaryImage.getMimeType(); - return response.setStatusCode(200).setStatusText("OK").setContentType(contentType).addHeader("Content-Disposition", "attachment; filename=" + temporaryImage.getId() + "." + contentType.substring(contentType.lastIndexOf('/') + 1)).write(temporaryImage.getImageData()); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLinkExcepted(URI link) { - return false; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java b/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java deleted file mode 100644 index 7a222d2..0000000 --- a/src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Sone - ImageBrowserPage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static com.google.common.collect.FluentIterable.from; -import static net.pterodactylus.sone.data.Album.FLATTENER; -import static net.pterodactylus.sone.data.Album.NOT_EMPTY; -import static net.pterodactylus.sone.data.Album.TITLE_COMPARATOR; -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import com.google.common.base.Optional; - -import net.pterodactylus.sone.data.Album; -import net.pterodactylus.sone.data.Image; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.collection.Pagination; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -/** - * The image browser page is the entry page for the image management. - * - * @author David ‘Bombe’ Roden - */ -public class ImageBrowserPage extends SoneTemplatePage { - - /** - * Creates a new image browser page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public ImageBrowserPage(Template template, WebInterface webInterface) { - super("imageBrowser.html", template, "Page.ImageBrowser.Title", webInterface, true); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - String albumId = request.getHttpRequest().getParam("album", null); - if (albumId != null) { - Album album = webInterface.getCore().getAlbum(albumId); - templateContext.set("albumRequested", true); - templateContext.set("album", album); - templateContext.set("page", request.getHttpRequest().getParam("page")); - return; - } - String imageId = request.getHttpRequest().getParam("image", null); - if (imageId != null) { - Image image = webInterface.getCore().getImage(imageId, false); - templateContext.set("imageRequested", true); - templateContext.set("image", image); - return; - } - String soneId = request.getHttpRequest().getParam("sone", null); - if (soneId != null) { - Optional sone = webInterface.getCore().getSone(soneId); - templateContext.set("soneRequested", true); - templateContext.set("sone", sone.orNull()); - return; - } - String mode = request.getHttpRequest().getParam("mode", null); - if ("gallery".equals(mode)) { - templateContext.set("galleryRequested", true); - List albums = new ArrayList(); - for (Sone sone : webInterface.getCore().getSones()) { - albums.addAll(from(sone.getRootAlbum().getAlbums()).transformAndConcat(FLATTENER).filter(NOT_EMPTY).toList()); - } - Collections.sort(albums, TITLE_COMPARATOR); - Pagination albumPagination = new Pagination(albums, 12).setPage(parseInt(request.getHttpRequest().getParam("page"), 0)); - templateContext.set("albumPagination", albumPagination); - templateContext.set("albums", albumPagination.getItems()); - return; - } - Sone sone = getCurrentSoneWithoutCreatingSession(request.getToadletContext()); - templateContext.set("soneRequested", true); - templateContext.set("sone", sone); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLinkExcepted(URI link) { - return true; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/IndexPage.java b/src/main/java/net/pterodactylus/sone/web/IndexPage.java deleted file mode 100644 index 1d40f45..0000000 --- a/src/main/java/net/pterodactylus/sone/web/IndexPage.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Sone - IndexPage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.notify.PostVisibilityFilter; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.collection.Pagination; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -import com.google.common.base.Optional; -import com.google.common.collect.Collections2; - -/** - * The index page shows the main page of Sone. This page will contain the posts - * of all friends of the current user. - * - * @author David ‘Bombe’ Roden - */ -public class IndexPage extends SoneTemplatePage { - - private final PostVisibilityFilter postVisibilityFilter; - - public IndexPage(Template template, WebInterface webInterface, PostVisibilityFilter postVisibilityFilter) { - super("index.html", template, "Page.Index.Title", webInterface, true); - this.postVisibilityFilter = postVisibilityFilter; - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - final Sone currentSone = getCurrentSone(request.getToadletContext()); - Collection allPosts = new ArrayList(); - allPosts.addAll(currentSone.getPosts()); - for (String friendSoneId : currentSone.getFriends()) { - Optional friendSone = webInterface.getCore().getSone(friendSoneId); - if (!friendSone.isPresent()) { - continue; - } - allPosts.addAll(friendSone.get().getPosts()); - } - for (Sone sone : webInterface.getCore().getSones()) { - for (Post post : sone.getPosts()) { - if (currentSone.equals(post.getRecipient().orNull()) && !allPosts.contains(post)) { - allPosts.add(post); - } - } - } - allPosts = Collections2.filter(allPosts, postVisibilityFilter.isVisible(currentSone)); - List sortedPosts = new ArrayList(allPosts); - Collections.sort(sortedPosts, Post.NEWEST_FIRST); - Pagination pagination = new Pagination(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0)); - templateContext.set("pagination", pagination); - templateContext.set("posts", pagination.getItems()); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java b/src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java deleted file mode 100644 index c74f4b9..0000000 --- a/src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Sone - KnownSonesPage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.collection.Pagination; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.Collections2; -import com.google.common.collect.Ordering; - -/** - * This page shows all known Sones. - * - * @author David ‘Bombe’ Roden - */ -public class KnownSonesPage extends SoneTemplatePage { - - private static final String defaultSortField = "activity"; - private static final String defaultSortOrder = "desc"; - - /** - * Creates a “known Sones” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public KnownSonesPage(Template template, WebInterface webInterface) { - super("knownSones.html", template, "Page.KnownSones.Title", webInterface, false); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - String sortField = request.getHttpRequest().getParam("sort", defaultSortField); - String sortOrder = request.getHttpRequest().getParam("order", defaultSortOrder); - String filter = request.getHttpRequest().getParam("filter"); - templateContext.set("sort", sortField); - templateContext.set("order", sortOrder); - templateContext.set("filter", filter); - final Sone currentSone = getCurrentSoneWithoutCreatingSession(request.getToadletContext()); - Collection knownSones = Collections2.filter(webInterface.getCore().getSones(), Sone.EMPTY_SONE_FILTER); - if ((currentSone != null) && "followed".equals(filter)) { - knownSones = Collections2.filter(knownSones, new Predicate() { - - @Override - public boolean apply(Sone sone) { - return currentSone.hasFriend(sone.getId()); - } - }); - } else if ((currentSone != null) && "not-followed".equals(filter)) { - knownSones = Collections2.filter(knownSones, new Predicate() { - - @Override - public boolean apply(Sone sone) { - return !currentSone.hasFriend(sone.getId()); - } - }); - } else if ("new".equals(filter)) { - knownSones = Collections2.filter(knownSones, new Predicate() { - - /** - * {@inheritDoc} - */ - @Override - public boolean apply(Sone sone) { - return !sone.isKnown(); - } - }); - } else if ("not-new".equals(filter)) { - knownSones = Collections2.filter(knownSones, new Predicate() { - - /** - * {@inheritDoc} - */ - @Override - public boolean apply(Sone sone) { - return sone.isKnown(); - } - }); - } else if ("own".equals(filter)) { - knownSones = Collections2.filter(knownSones, Sone.LOCAL_SONE_FILTER); - } else if ("not-own".equals(filter)) { - knownSones = Collections2.filter(knownSones, Predicates.not(Sone.LOCAL_SONE_FILTER)); - } - List sortedSones = new ArrayList(knownSones); - if ("activity".equals(sortField)) { - if ("asc".equals(sortOrder)) { - Collections.sort(sortedSones, Ordering.from(Sone.LAST_ACTIVITY_COMPARATOR).reverse()); - } else { - Collections.sort(sortedSones, Sone.LAST_ACTIVITY_COMPARATOR); - } - } else if ("posts".equals(sortField)) { - if ("asc".equals(sortOrder)) { - Collections.sort(sortedSones, Ordering.from(Sone.POST_COUNT_COMPARATOR).reverse()); - } else { - Collections.sort(sortedSones, Sone.POST_COUNT_COMPARATOR); - } - } else if ("images".equals(sortField)) { - if ("asc".equals(sortOrder)) { - Collections.sort(sortedSones, Ordering.from(Sone.IMAGE_COUNT_COMPARATOR).reverse()); - } else { - Collections.sort(sortedSones, Sone.IMAGE_COUNT_COMPARATOR); - } - } else { - if ("desc".equals(sortOrder)) { - Collections.sort(sortedSones, Ordering.from(Sone.NICE_NAME_COMPARATOR).reverse()); - } else { - Collections.sort(sortedSones, Sone.NICE_NAME_COMPARATOR); - } - } - Pagination sonePagination = new Pagination(sortedSones, 25).setPage(parseInt(request.getHttpRequest().getParam("page"), 0)); - templateContext.set("pagination", sonePagination); - templateContext.set("knownSones", sonePagination.getItems()); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/LikePage.java b/src/main/java/net/pterodactylus/sone/web/LikePage.java deleted file mode 100644 index fc39eff..0000000 --- a/src/main/java/net/pterodactylus/sone/web/LikePage.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Sone - LikePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * Page that lets the user like a {@link Post}. - * - * @author David ‘Bombe’ Roden - */ -public class LikePage extends SoneTemplatePage { - - /** - * Creates a new “like post” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public LikePage(Template template, WebInterface webInterface) { - super("like.html", template, "Page.Like.Title", webInterface, true); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - String type = request.getHttpRequest().getPartAsStringFailsafe("type", 16); - String id = request.getHttpRequest().getPartAsStringFailsafe(type, 36); - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - Sone currentSone = getCurrentSone(request.getToadletContext()); - if ("post".equals(type)) { - currentSone.addLikedPostId(id); - } else if ("reply".equals(type)) { - currentSone.addLikedReplyId(id); - } - throw new RedirectException(returnPage); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/LockSonePage.java b/src/main/java/net/pterodactylus/sone/web/LockSonePage.java deleted file mode 100644 index a0a9574..0000000 --- a/src/main/java/net/pterodactylus/sone/web/LockSonePage.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Sone - LockSonePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -/** - * This page lets the user lock a {@link Sone} to prevent it from being - * inserted. - * - * @author David ‘Bombe’ Roden - */ -public class LockSonePage extends SoneTemplatePage { - - /** - * Creates a new “lock Sone” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public LockSonePage(Template template, WebInterface webInterface) { - super("lockSone.html", template, "Page.LockSone.Title", webInterface); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); - Sone sone = webInterface.getCore().getLocalSone(soneId); - if (sone != null) { - webInterface.getCore().lockSone(sone); - } - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - throw new RedirectException(returnPage); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/LogoutPage.java b/src/main/java/net/pterodactylus/sone/web/LogoutPage.java deleted file mode 100644 index 3ec62a3..0000000 --- a/src/main/java/net/pterodactylus/sone/web/LogoutPage.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Sone - LogoutPage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import freenet.clients.http.ToadletContext; - -/** - * Logs a user out. - * - * @author David ‘Bombe’ Roden - */ -public class LogoutPage extends SoneTemplatePage { - - /** - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public LogoutPage(Template template, WebInterface webInterface) { - super("logout.html", template, "Page.Logout.Title", webInterface, true); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - setCurrentSone(request.getToadletContext(), null); - throw new RedirectException("index.html"); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isEnabled(ToadletContext toadletContext) { - if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) { - return false; - } - return (getCurrentSoneWithoutCreatingSession(toadletContext) != null) && (webInterface.getCore().getLocalSones().size() != 1); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java b/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java deleted file mode 100644 index 57ea00f..0000000 --- a/src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Sone - MarkAsKnownPage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import java.util.StringTokenizer; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.PostReply; -import net.pterodactylus.sone.data.Reply; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -import com.google.common.base.Optional; - -/** - * Page that lets the user mark a number of {@link Sone}s, {@link Post}s, or - * {@link Reply Replie}s as known. - * - * @author David ‘Bombe’ Roden - */ -public class MarkAsKnownPage extends SoneTemplatePage { - - /** - * Creates a new “mark as known” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public MarkAsKnownPage(Template template, WebInterface webInterface) { - super("markAsKnown.html", template, "Page.MarkAsKnown.Title", webInterface); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - String type = request.getHttpRequest().getPartAsStringFailsafe("type", 5); - if (!type.equals("sone") && !type.equals("post") && !type.equals("reply")) { - throw new RedirectException("invalid.html"); - } - String ids = request.getHttpRequest().getPartAsStringFailsafe("id", 65536); - for (StringTokenizer idTokenizer = new StringTokenizer(ids); idTokenizer.hasMoreTokens();) { - String id = idTokenizer.nextToken(); - if (type.equals("post")) { - Optional post = webInterface.getCore().getPost(id); - if (!post.isPresent()) { - continue; - } - webInterface.getCore().markPostKnown(post.get()); - } else if (type.equals("reply")) { - Optional reply = webInterface.getCore().getPostReply(id); - if (!reply.isPresent()) { - continue; - } - webInterface.getCore().markReplyKnown(reply.get()); - } else { - Optional sone = webInterface.getCore().getSone(id); - if (!sone.isPresent()) { - continue; - } - webInterface.getCore().markSoneKnown(sone.get()); - } - } - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - throw new RedirectException(returnPage); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/NewPage.java b/src/main/java/net/pterodactylus/sone/web/NewPage.java deleted file mode 100644 index 9eab69a..0000000 --- a/src/main/java/net/pterodactylus/sone/web/NewPage.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Sone - NewPage.java - Copyright © 2013–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.PostReply; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.notify.PostVisibilityFilter; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.collection.Pagination; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -/** - * Page that displays all new posts and replies. The posts are filtered using - * {@link PostVisibilityFilter#isPostVisible(Sone, Post)} and sorted by time. - * - * @author David ‘Bombe’ Roden - */ -public class NewPage extends SoneTemplatePage { - - /** - * Creates a new “new posts and replies” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public NewPage(Template template, WebInterface webInterface) { - super("new.html", template, "Page.New.Title", webInterface); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - /* collect new elements from notifications. */ - Set posts = new HashSet(webInterface.getNewPosts(getCurrentSoneWithoutCreatingSession(request.getToadletContext()))); - for (PostReply reply : webInterface.getNewReplies(getCurrentSoneWithoutCreatingSession(request.getToadletContext()))) { - posts.add(reply.getPost().get()); - } - - /* filter and sort them. */ - List sortedPosts = new ArrayList<>(posts); - Collections.sort(sortedPosts, Post.NEWEST_FIRST); - - /* paginate them. */ - Pagination pagination = new Pagination<>(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0)); - templateContext.set("pagination", pagination); - templateContext.set("posts", pagination.getItems()); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java b/src/main/java/net/pterodactylus/sone/web/OptionsPage.java deleted file mode 100644 index 7cf7246..0000000 --- a/src/main/java/net/pterodactylus/sone/web/OptionsPage.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Sone - OptionsPage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.util.ArrayList; -import java.util.List; - -import net.pterodactylus.sone.core.Preferences; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent; -import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * This page lets the user edit the options of the Sone plugin. - * - * @author David ‘Bombe’ Roden - */ -public class OptionsPage extends SoneTemplatePage { - - /** - * Creates a new options page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public OptionsPage(Template template, WebInterface webInterface) { - super("options.html", template, "Page.Options.Title", webInterface, false); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - Preferences preferences = webInterface.getCore().getPreferences(); - Sone currentSone = webInterface.getCurrentSoneWithoutCreatingSession(request.getToadletContext()); - if (request.getMethod() == Method.POST) { - List fieldErrors = new ArrayList(); - if (currentSone != null) { - boolean autoFollow = request.getHttpRequest().isPartSet("auto-follow"); - currentSone.getOptions().setAutoFollow(autoFollow); - boolean enableSoneInsertNotifications = request.getHttpRequest().isPartSet("enable-sone-insert-notifications"); - currentSone.getOptions().setSoneInsertNotificationEnabled(enableSoneInsertNotifications); - boolean showNotificationNewSones = request.getHttpRequest().isPartSet("show-notification-new-sones"); - currentSone.getOptions().setShowNewSoneNotifications(showNotificationNewSones); - boolean showNotificationNewPosts = request.getHttpRequest().isPartSet("show-notification-new-posts"); - currentSone.getOptions().setShowNewPostNotifications(showNotificationNewPosts); - boolean showNotificationNewReplies = request.getHttpRequest().isPartSet("show-notification-new-replies"); - currentSone.getOptions().setShowNewReplyNotifications(showNotificationNewReplies); - String showCustomAvatars = request.getHttpRequest().getPartAsStringFailsafe("show-custom-avatars", 32); - currentSone.getOptions().setShowCustomAvatars(LoadExternalContent.valueOf(showCustomAvatars)); - String loadLinkedImages = request.getHttpRequest().getPartAsStringFailsafe("load-linked-images", 32); - currentSone.getOptions().setLoadLinkedImages(LoadExternalContent.valueOf(loadLinkedImages)); - webInterface.getCore().touchConfiguration(); - } - Integer insertionDelay = parseInt(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16), null); - if (!preferences.validateInsertionDelay(insertionDelay)) { - fieldErrors.add("insertion-delay"); - } else { - preferences.setInsertionDelay(insertionDelay); - } - Integer postsPerPage = parseInt(request.getHttpRequest().getPartAsStringFailsafe("posts-per-page", 4), null); - if (!preferences.validatePostsPerPage(postsPerPage)) { - fieldErrors.add("posts-per-page"); - } else { - preferences.setPostsPerPage(postsPerPage); - } - Integer imagesPerPage = parseInt(request.getHttpRequest().getPartAsStringFailsafe("images-per-page", 4), null); - if (!preferences.validateImagesPerPage(imagesPerPage)) { - fieldErrors.add("images-per-page"); - } else { - preferences.setImagesPerPage(imagesPerPage); - } - Integer charactersPerPost = parseInt(request.getHttpRequest().getPartAsStringFailsafe("characters-per-post", 10), null); - if (!preferences.validateCharactersPerPost(charactersPerPost)) { - fieldErrors.add("characters-per-post"); - } else { - preferences.setCharactersPerPost(charactersPerPost); - } - Integer postCutOffLength = parseInt(request.getHttpRequest().getPartAsStringFailsafe("post-cut-off-length", 10), null); - if (!preferences.validatePostCutOffLength(postCutOffLength)) { - fieldErrors.add("post-cut-off-length"); - } else { - preferences.setPostCutOffLength(postCutOffLength); - } - boolean requireFullAccess = request.getHttpRequest().isPartSet("require-full-access"); - preferences.setRequireFullAccess(requireFullAccess); - Integer positiveTrust = parseInt(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3), null); - if (!preferences.validatePositiveTrust(positiveTrust)) { - fieldErrors.add("positive-trust"); - } else { - preferences.setPositiveTrust(positiveTrust); - } - Integer negativeTrust = parseInt(request.getHttpRequest().getPartAsStringFailsafe("negative-trust", 4), null); - if (!preferences.validateNegativeTrust(negativeTrust)) { - fieldErrors.add("negative-trust"); - } else { - preferences.setNegativeTrust(negativeTrust); - } - String trustComment = request.getHttpRequest().getPartAsStringFailsafe("trust-comment", 256); - if (trustComment.trim().length() == 0) { - trustComment = null; - } - preferences.setTrustComment(trustComment); - boolean fcpInterfaceActive = request.getHttpRequest().isPartSet("fcp-interface-active"); - preferences.setFcpInterfaceActive(fcpInterfaceActive); - Integer fcpFullAccessRequiredInteger = parseInt(request.getHttpRequest().getPartAsStringFailsafe("fcp-full-access-required", 1), preferences.getFcpFullAccessRequired().ordinal()); - FullAccessRequired fcpFullAccessRequired = FullAccessRequired.values()[fcpFullAccessRequiredInteger]; - preferences.setFcpFullAccessRequired(fcpFullAccessRequired); - webInterface.getCore().touchConfiguration(); - if (fieldErrors.isEmpty()) { - throw new RedirectException(getPath()); - } - templateContext.set("fieldErrors", fieldErrors); - } - if (currentSone != null) { - templateContext.set("auto-follow", currentSone.getOptions().isAutoFollow()); - templateContext.set("enable-sone-insert-notifications", currentSone.getOptions().isSoneInsertNotificationEnabled()); - templateContext.set("show-notification-new-sones", currentSone.getOptions().isShowNewSoneNotifications()); - templateContext.set("show-notification-new-posts", currentSone.getOptions().isShowNewPostNotifications()); - templateContext.set("show-notification-new-replies", currentSone.getOptions().isShowNewReplyNotifications()); - templateContext.set("show-custom-avatars", currentSone.getOptions().getShowCustomAvatars().name()); - templateContext.set("load-linked-images", currentSone.getOptions().getLoadLinkedImages().name()); - } - templateContext.set("insertion-delay", preferences.getInsertionDelay()); - templateContext.set("posts-per-page", preferences.getPostsPerPage()); - templateContext.set("images-per-page", preferences.getImagesPerPage()); - templateContext.set("characters-per-post", preferences.getCharactersPerPost()); - templateContext.set("post-cut-off-length", preferences.getPostCutOffLength()); - templateContext.set("require-full-access", preferences.isRequireFullAccess()); - templateContext.set("positive-trust", preferences.getPositiveTrust()); - templateContext.set("negative-trust", preferences.getNegativeTrust()); - templateContext.set("trust-comment", preferences.getTrustComment()); - templateContext.set("fcp-interface-active", preferences.isFcpInterfaceActive()); - templateContext.set("fcp-full-access-required", preferences.getFcpFullAccessRequired().ordinal()); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/ReloadingPage.java b/src/main/java/net/pterodactylus/sone/web/ReloadingPage.java deleted file mode 100644 index bc18816..0000000 --- a/src/main/java/net/pterodactylus/sone/web/ReloadingPage.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Sone - ReloadingPage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import net.pterodactylus.util.io.StreamCopier; -import net.pterodactylus.util.web.Page; -import net.pterodactylus.util.web.Request; -import net.pterodactylus.util.web.Response; - -/** - * {@link Page} implementation that delivers static files from the filesystem. - * - * @param - * The type of the request - * @author David ‘Bombe’ Roden - */ -public class ReloadingPage implements Page { - - private final String pathPrefix; - private final String filesystemPath; - private final String mimeType; - - public ReloadingPage(String pathPrefix, String filesystemPathPrefix, String mimeType) { - this.pathPrefix = pathPrefix; - this.filesystemPath = filesystemPathPrefix; - this.mimeType = mimeType; - } - - /** - * {@inheritDoc} - */ - @Override - public String getPath() { - return pathPrefix; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isPrefixPage() { - return true; - } - - /** - * {@inheritDoc} - */ - @Override - public Response handleRequest(REQ request, Response response) throws IOException { - String path = request.getUri().getPath(); - int lastSlash = path.lastIndexOf('/'); - String filename = path.substring(lastSlash + 1); - try (InputStream fileInputStream = new FileInputStream(new File(filesystemPath, filename)); - OutputStream contentOutputStream = response.getContent()) { - StreamCopier.copy(fileInputStream, contentOutputStream); - } catch (FileNotFoundException fnfe1) { - return response.setStatusCode(404).setStatusText("Not found."); - } - return response.setStatusCode(200).setStatusText("OK").setContentType(mimeType); - } -} diff --git a/src/main/java/net/pterodactylus/sone/web/RescuePage.java b/src/main/java/net/pterodactylus/sone/web/RescuePage.java deleted file mode 100644 index 0f94563..0000000 --- a/src/main/java/net/pterodactylus/sone/web/RescuePage.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Sone - RescuePage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static net.pterodactylus.sone.utils.NumberParsers.parseLong; - -import net.pterodactylus.sone.core.SoneRescuer; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * Page that lets the user control the rescue mode for a Sone. - * - * @see SoneRescuer - * @author David ‘Bombe’ Roden - */ -public class RescuePage extends SoneTemplatePage { - - /** - * Creates a new rescue page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public RescuePage(Template template, WebInterface webInterface) { - super("rescue.html", template, "Page.Rescue.Title", webInterface, true); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - Sone currentSone = getCurrentSoneWithoutCreatingSession(request.getToadletContext()); - SoneRescuer soneRescuer = webInterface.getCore().getSoneRescuer(currentSone); - if (request.getMethod() == Method.POST) { - if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("fetch", 4))) { - long edition = parseLong(request.getHttpRequest().getPartAsStringFailsafe("edition", 8), -1L); - if (edition > -1) { - soneRescuer.setEdition(edition); - } - soneRescuer.startNextFetch(); - } - throw new RedirectException("rescue.html"); - } - templateContext.set("soneRescuer", soneRescuer); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/SearchPage.java b/src/main/java/net/pterodactylus/sone/web/SearchPage.java deleted file mode 100644 index 8b444a7..0000000 --- a/src/main/java/net/pterodactylus/sone/web/SearchPage.java +++ /dev/null @@ -1,658 +0,0 @@ -/* - * Sone - SearchPage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static com.google.common.base.Optional.fromNullable; -import static com.google.common.primitives.Ints.tryParse; -import static java.util.logging.Logger.getLogger; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.PostReply; -import net.pterodactylus.sone.data.Profile; -import net.pterodactylus.sone.data.Profile.Field; -import net.pterodactylus.sone.data.Reply; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.collection.Pagination; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.text.StringEscaper; -import net.pterodactylus.util.text.TextException; - -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.collect.Collections2; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Ordering; - -/** - * This page lets the user search for posts and replies that contain certain - * words. - * - * @author David ‘Bombe’ Roden - */ -public class SearchPage extends SoneTemplatePage { - - /** The logger. */ - private static final Logger logger = getLogger(SearchPage.class.getName()); - - /** Short-term cache. */ - private final LoadingCache, Set>> hitCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader, Set>>() { - - @Override - @SuppressWarnings("synthetic-access") - public Set> load(List phrases) { - Set posts = new HashSet(); - for (Sone sone : webInterface.getCore().getSones()) { - posts.addAll(sone.getPosts()); - } - return getHits(Collections2.filter(posts, Post.FUTURE_POSTS_FILTER), phrases, new PostStringGenerator()); - } - }); - - /** - * Creates a new search page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public SearchPage(Template template, WebInterface webInterface) { - super("search.html", template, "Page.Search.Title", webInterface); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - @SuppressWarnings("synthetic-access") - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - String query = request.getHttpRequest().getParam("query").trim(); - if (query.length() == 0) { - throw new RedirectException("index.html"); - } - - List phrases = parseSearchPhrases(query); - if (phrases.isEmpty()) { - throw new RedirectException("index.html"); - } - - /* check for a couple of shortcuts. */ - if (phrases.size() == 1) { - String phrase = phrases.get(0).getPhrase(); - - /* is it a Sone ID? */ - redirectIfNotNull(getSoneId(phrase), "viewSone.html?sone="); - - /* is it a post ID? */ - redirectIfNotNull(getPostId(phrase), "viewPost.html?post="); - - /* is it a reply ID? show the post. */ - redirectIfNotNull(getReplyPostId(phrase), "viewPost.html?post="); - - /* is it an album ID? */ - redirectIfNotNull(getAlbumId(phrase), "imageBrowser.html?album="); - - /* is it an image ID? */ - redirectIfNotNull(getImageId(phrase), "imageBrowser.html?image="); - } - - Collection sones = webInterface.getCore().getSones(); - Collection> soneHits = getHits(sones, phrases, SoneStringGenerator.COMPLETE_GENERATOR); - - Collection> postHits = hitCache.getUnchecked(phrases); - - /* now filter. */ - soneHits = Collections2.filter(soneHits, Hit.POSITIVE_FILTER); - postHits = Collections2.filter(postHits, Hit.POSITIVE_FILTER); - - /* now sort. */ - List> sortedSoneHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(soneHits); - List> sortedPostHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(postHits); - - /* extract Sones and posts. */ - List resultSones = FluentIterable.from(sortedSoneHits).transform(new HitMapper()).toList(); - List resultPosts = FluentIterable.from(sortedPostHits).transform(new HitMapper()).toList(); - - /* pagination. */ - Pagination sonePagination = new Pagination(resultSones, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(fromNullable(tryParse(request.getHttpRequest().getParam("sonePage"))).or(0)); - Pagination postPagination = new Pagination(resultPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(fromNullable(tryParse(request.getHttpRequest().getParam("postPage"))).or(0)); - - templateContext.set("sonePagination", sonePagination); - templateContext.set("soneHits", sonePagination.getItems()); - templateContext.set("postPagination", postPagination); - templateContext.set("postHits", postPagination.getItems()); - } - - // - // PRIVATE METHODS - // - - /** - * Collects hit information for the given objects. The objects are converted - * to a {@link String} using the given {@link StringGenerator}, and the - * {@link #calculateScore(List, String) calculated score} is stored together - * with the object in a {@link Hit}, and all resulting {@link Hit}s are then - * returned. - * - * @param - * The type of the objects - * @param objects - * The objects to search over - * @param phrases - * The phrases to search for - * @param stringGenerator - * The string generator for the objects - * @return The hits for the given phrases - */ - private static Set> getHits(Collection objects, List phrases, StringGenerator stringGenerator) { - Set> hits = new HashSet>(); - for (T object : objects) { - String objectString = stringGenerator.generateString(object); - double score = calculateScore(phrases, objectString); - hits.add(new Hit(object, score)); - } - return hits; - } - - /** - * Parses the given query into search phrases. The query is split on - * whitespace while allowing to group words using single or double quotes. - * Isolated phrases starting with a “+” are - * {@link Phrase.Optionality#REQUIRED}, phrases with a “-” are - * {@link Phrase.Optionality#FORBIDDEN}. - * - * @param query - * The query to parse - * @return The parsed phrases - */ - private static List parseSearchPhrases(String query) { - List parsedPhrases; - try { - parsedPhrases = StringEscaper.parseLine(query); - } catch (TextException te1) { - /* invalid query. */ - return Collections.emptyList(); - } - - List phrases = new ArrayList(); - for (String phrase : parsedPhrases) { - if (phrase.startsWith("+")) { - if (phrase.length() > 1) { - phrases.add(new Phrase(phrase.substring(1), Phrase.Optionality.REQUIRED)); - } else { - phrases.add(new Phrase("+", Phrase.Optionality.OPTIONAL)); - } - } else if (phrase.startsWith("-")) { - if (phrase.length() > 1) { - phrases.add(new Phrase(phrase.substring(1), Phrase.Optionality.FORBIDDEN)); - } else { - phrases.add(new Phrase("-", Phrase.Optionality.OPTIONAL)); - } - } else { - phrases.add(new Phrase(phrase, Phrase.Optionality.OPTIONAL)); - } - } - return phrases; - } - - /** - * Calculates the score for the given expression when using the given - * phrases. - * - * @param phrases - * The phrases to search for - * @param expression - * The expression to search - * @return The score of the expression - */ - private static double calculateScore(List phrases, String expression) { - logger.log(Level.FINEST, String.format("Calculating Score for “%s”…", expression)); - double optionalHits = 0; - double requiredHits = 0; - int forbiddenHits = 0; - int requiredPhrases = 0; - for (Phrase phrase : phrases) { - String phraseString = phrase.getPhrase().toLowerCase(); - if (phrase.getOptionality() == Phrase.Optionality.REQUIRED) { - ++requiredPhrases; - } - int matches = 0; - int index = 0; - double score = 0; - while (index < expression.length()) { - int position = expression.toLowerCase().indexOf(phraseString, index); - if (position == -1) { - break; - } - score += Math.pow(1 - position / (double) expression.length(), 2); - index = position + phraseString.length(); - logger.log(Level.FINEST, String.format("Got hit at position %d.", position)); - ++matches; - } - logger.log(Level.FINEST, String.format("Score: %f", score)); - if (matches == 0) { - continue; - } - if (phrase.getOptionality() == Phrase.Optionality.REQUIRED) { - requiredHits += score; - } - if (phrase.getOptionality() == Phrase.Optionality.OPTIONAL) { - optionalHits += score; - } - if (phrase.getOptionality() == Phrase.Optionality.FORBIDDEN) { - forbiddenHits += matches; - } - } - return requiredHits * 3 + optionalHits + (requiredHits - requiredPhrases) * 5 - (forbiddenHits * 2); - } - - /** - * Throws a - * {@link net.pterodactylus.sone.web.page.FreenetTemplatePage.RedirectException} - * if the given object is not {@code null}, appending the object to the - * given target URL. - * - * @param object - * The object on which to redirect - * @param target - * The target of the redirect - * @throws RedirectException - * if {@code object} is not {@code null} - */ - private static void redirectIfNotNull(String object, String target) throws RedirectException { - if (object != null) { - throw new RedirectException(target + object); - } - } - - /** - * If the given phrase contains a Sone ID (optionally prefixed by - * “sone://”), returns said Sone ID, otherwise return {@code null}. - * - * @param phrase - * The phrase that maybe is a Sone ID - * @return The Sone ID, or {@code null} - */ - private String getSoneId(String phrase) { - String soneId = phrase.startsWith("sone://") ? phrase.substring(7) : phrase; - return (webInterface.getCore().getSone(soneId).isPresent()) ? soneId : null; - } - - /** - * If the given phrase contains a post ID (optionally prefixed by - * “post://”), returns said post ID, otherwise return {@code null}. - * - * @param phrase - * The phrase that maybe is a post ID - * @return The post ID, or {@code null} - */ - private String getPostId(String phrase) { - String postId = phrase.startsWith("post://") ? phrase.substring(7) : phrase; - return (webInterface.getCore().getPost(postId).isPresent()) ? postId : null; - } - - /** - * If the given phrase contains a reply ID (optionally prefixed by - * “reply://”), returns the ID of the post the reply belongs to, otherwise - * return {@code null}. - * - * @param phrase - * The phrase that maybe is a reply ID - * @return The reply’s post ID, or {@code null} - */ - private String getReplyPostId(String phrase) { - String replyId = phrase.startsWith("reply://") ? phrase.substring(8) : phrase; - Optional postReply = webInterface.getCore().getPostReply(replyId); - if (!postReply.isPresent()) { - return null; - } - return postReply.get().getPostId(); - } - - /** - * If the given phrase contains an album ID (optionally prefixed by - * “album://”), returns said album ID, otherwise return {@code null}. - * - * @param phrase - * The phrase that maybe is an album ID - * @return The album ID, or {@code null} - */ - private String getAlbumId(String phrase) { - String albumId = phrase.startsWith("album://") ? phrase.substring(8) : phrase; - return (webInterface.getCore().getAlbum(albumId) != null) ? albumId : null; - } - - /** - * If the given phrase contains an image ID (optionally prefixed by - * “image://”), returns said image ID, otherwise return {@code null}. - * - * @param phrase - * The phrase that maybe is an image ID - * @return The image ID, or {@code null} - */ - private String getImageId(String phrase) { - String imageId = phrase.startsWith("image://") ? phrase.substring(8) : phrase; - return (webInterface.getCore().getImage(imageId, false) != null) ? imageId : null; - } - - /** - * Converts a given object into a {@link String}. - * - * @param - * The type of the objects - * @author David ‘Bombe’ Roden - */ - private static interface StringGenerator { - - /** - * Generates a {@link String} for the given object. - * - * @param object - * The object to generate the {@link String} for - * @return The generated {@link String} - */ - public String generateString(T object); - - } - - /** - * Generates a {@link String} from a {@link Sone}, concatenating the name of - * the Sone and all {@link Profile} {@link Field} values. - * - * @author David ‘Bombe’ Roden - */ - private static class SoneStringGenerator implements StringGenerator { - - /** A static instance of a complete Sone string generator. */ - public static final SoneStringGenerator COMPLETE_GENERATOR = new SoneStringGenerator(true); - - /** - * A static instance of a Sone string generator that will only use the - * name of the Sone. - */ - public static final SoneStringGenerator NAME_GENERATOR = new SoneStringGenerator(false); - - /** Whether to generate a string from all data of a Sone. */ - private final boolean complete; - - /** - * Creates a new Sone string generator. - * - * @param complete - * {@code true} to use the profile’s fields, {@code false} to - * not to use the profile‘s fields - */ - private SoneStringGenerator(boolean complete) { - this.complete = complete; - } - - /** - * {@inheritDoc} - */ - @Override - public String generateString(Sone sone) { - StringBuilder soneString = new StringBuilder(); - soneString.append(sone.getName()); - Profile soneProfile = sone.getProfile(); - if (soneProfile.getFirstName() != null) { - soneString.append(' ').append(soneProfile.getFirstName()); - } - if (soneProfile.getMiddleName() != null) { - soneString.append(' ').append(soneProfile.getMiddleName()); - } - if (soneProfile.getLastName() != null) { - soneString.append(' ').append(soneProfile.getLastName()); - } - if (complete) { - for (Field field : soneProfile.getFields()) { - soneString.append(' ').append(field.getValue()); - } - } - return soneString.toString(); - } - - } - - /** - * Generates a {@link String} from a {@link Post}, concatenating the text of - * the post, the text of all {@link Reply}s, and the name of all - * {@link Sone}s that have replied. - * - * @author David ‘Bombe’ Roden - */ - private class PostStringGenerator implements StringGenerator { - - /** - * {@inheritDoc} - */ - @Override - public String generateString(Post post) { - StringBuilder postString = new StringBuilder(); - postString.append(post.getText()); - if (post.getRecipient().isPresent()) { - postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(post.getRecipient().get())); - } - for (PostReply reply : Collections2.filter(webInterface.getCore().getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER)) { - postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(reply.getSone())); - postString.append(' ').append(reply.getText()); - } - return postString.toString(); - } - - } - - /** - * A search phrase. - * - * @author David ‘Bombe’ Roden - */ - private static class Phrase { - - /** - * The optionality of a search phrase. - * - * @author David ‘Bombe’ - * Roden - */ - public enum Optionality { - - /** The phrase is optional. */ - OPTIONAL, - - /** The phrase is required. */ - REQUIRED, - - /** The phrase is forbidden. */ - FORBIDDEN - - } - - /** The phrase to search for. */ - private final String phrase; - - /** The optionality of the phrase. */ - private final Optionality optionality; - - /** - * Creates a new phrase. - * - * @param phrase - * The phrase to search for - * @param optionality - * The optionality of the phrase - */ - public Phrase(String phrase, Optionality optionality) { - this.optionality = optionality; - this.phrase = phrase; - } - - /** - * Returns the phrase to search for. - * - * @return The phrase to search for - */ - public String getPhrase() { - return phrase; - } - - /** - * Returns the optionality of the phrase. - * - * @return The optionality of the phrase - */ - public Optionality getOptionality() { - return optionality; - } - - // - // OBJECT METHODS - // - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return phrase.hashCode() ^ ((optionality == Optionality.FORBIDDEN) ? (0xaaaaaaaa) : ((optionality == Optionality.REQUIRED) ? 0x55555555 : 0)); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object object) { - if (!(object instanceof Phrase)) { - return false; - } - Phrase phrase = (Phrase) object; - return (this.optionality == phrase.optionality) && this.phrase.equals(phrase.phrase); - } - - } - - /** - * A hit consists of a searched object and the score it got for the phrases - * of the search. - * - * @see SearchPage#calculateScore(List, String) - * @param - * The type of the searched object - * @author David ‘Bombe’ Roden - */ - private static class Hit { - - /** Filter for {@link Hit}s with a score of more than 0. */ - public static final Predicate> POSITIVE_FILTER = new Predicate>() { - - @Override - public boolean apply(Hit hit) { - return (hit != null) && (hit.getScore() > 0); - } - - }; - - /** Comparator that sorts {@link Hit}s descending by score. */ - public static final Comparator> DESCENDING_COMPARATOR = new Comparator>() { - - @Override - public int compare(Hit leftHit, Hit rightHit) { - return Double.compare(rightHit.getScore(), leftHit.getScore()); - } - - }; - - /** The object that was searched. */ - private final T object; - - /** The score of the object. */ - private final double score; - - /** - * Creates a new hit. - * - * @param object - * The object that was searched - * @param score - * The score of the object - */ - public Hit(T object, double score) { - this.object = object; - this.score = score; - } - - /** - * Returns the object that was searched. - * - * @return The object that was searched - */ - public T getObject() { - return object; - } - - /** - * Returns the score of the object. - * - * @return The score of the object - */ - public double getScore() { - return score; - } - - } - - /** - * Extracts the object from a {@link Hit}. - * - * @param - * The type of the object to extract - * @author David ‘Bombe’ Roden - */ - private static class HitMapper implements Function, T> { - - /** - * {@inheritDoc} - */ - @Override - public T apply(Hit input) { - return input.getObject(); - } - - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java b/src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java deleted file mode 100644 index bece45d..0000000 --- a/src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Sone - SoneTemplatePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nonnull; - -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.main.SonePlugin; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.sone.web.page.FreenetTemplatePage; -import net.pterodactylus.util.notify.Notification; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -import freenet.clients.http.SessionManager.Session; -import freenet.clients.http.ToadletContext; -import freenet.support.api.HTTPRequest; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; - -/** - * Base page for the Sone web interface. - * - * @author David ‘Bombe’ Roden - */ -public class SoneTemplatePage extends FreenetTemplatePage { - - /** The Sone core. */ - protected final WebInterface webInterface; - - /** The page title l10n key. */ - private final String pageTitleKey; - - /** Whether to require a login. */ - private final boolean requireLogin; - - /** - * Creates a new template page for Sone that does not require the user to be - * logged in. - * - * @param path - * The path of the page - * @param template - * The template to render - * @param pageTitleKey - * The l10n key of the page title - * @param webInterface - * The Sone web interface - */ - public SoneTemplatePage(String path, Template template, String pageTitleKey, WebInterface webInterface) { - this(path, template, pageTitleKey, webInterface, false); - } - - /** - * Creates a new template page for Sone. - * - * @param path - * The path of the page - * @param template - * The template to render - * @param webInterface - * The Sone web interface - * @param requireLogin - * Whether this page requires a login - */ - public SoneTemplatePage(String path, Template template, WebInterface webInterface, boolean requireLogin) { - this(path, template, null, webInterface, requireLogin); - } - - /** - * Creates a new template page for Sone. - * - * @param path - * The path of the page - * @param template - * The template to render - * @param pageTitleKey - * The l10n key of the page title - * @param webInterface - * The Sone web interface - * @param requireLogin - * Whether this page requires a login - */ - public SoneTemplatePage(String path, Template template, String pageTitleKey, WebInterface webInterface, boolean requireLogin) { - super(path, webInterface.getTemplateContextFactory(), template, "noPermission.html"); - this.pageTitleKey = pageTitleKey; - this.webInterface = webInterface; - this.requireLogin = requireLogin; - } - - // - // PROTECTED METHODS - // - - /** - * Returns the currently logged in Sone. - * - * @param toadletContext - * The toadlet context - * @return The currently logged in Sone, or {@code null} if no Sone is - * currently logged in - */ - protected Sone getCurrentSone(ToadletContext toadletContext) { - return webInterface.getCurrentSoneCreatingSession(toadletContext); - } - - protected Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) { - return webInterface.getCurrentSoneWithoutCreatingSession(toadletContext); - } - - /** - * Sets the currently logged in Sone. - * - * @param toadletContext - * The toadlet context - * @param sone - * The Sone to set as currently logged in - */ - protected void setCurrentSone(ToadletContext toadletContext, Sone sone) { - webInterface.setCurrentSone(toadletContext, sone); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected String getPageTitle(FreenetRequest request) { - if (pageTitleKey != null) { - return webInterface.getL10n().getString(pageTitleKey); - } - return ""; - } - - /** - * {@inheritDoc} - */ - @Override - protected List> getAdditionalLinkNodes(FreenetRequest request) { - return ImmutableList.> builder().add(ImmutableMap. builder().put("rel", "search").put("type", "application/opensearchdescription+xml").put("title", "Sone").put("href", "http://" + request.getHttpRequest().getHeader("host") + "/Sone/OpenSearch.xml").build()).build(); - } - - /** - * {@inheritDoc} - */ - @Override - protected Collection getStyleSheets() { - return Arrays.asList("css/sone.css"); - } - - /** - * {@inheritDoc} - */ - @Override - protected String getShortcutIcon() { - return "images/icon.png"; - } - - /** - * Returns whether this page requires the user to log in. - * - * @return {@code true} if the user is required to be logged in to use this - * page, {@code false} otherwise - */ - protected boolean requiresLogin() { - return requireLogin; - } - - /** - * {@inheritDoc} - */ - @Override - protected final void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - super.processTemplate(request, templateContext); - Sone currentSone = getCurrentSoneWithoutCreatingSession(request.getToadletContext()); - templateContext.set("preferences", webInterface.getCore().getPreferences()); - templateContext.set("currentSone", currentSone); - templateContext.set("localSones", webInterface.getCore().getLocalSones()); - templateContext.set("request", request); - templateContext.set("currentVersion", SonePlugin.getPluginVersion()); - templateContext.set("hasLatestVersion", webInterface.getCore().getUpdateChecker().hasLatestVersion()); - templateContext.set("latestEdition", webInterface.getCore().getUpdateChecker().getLatestEdition()); - templateContext.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion()); - templateContext.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate()); - List notifications = new ArrayList(webInterface.getNotifications(currentSone)); - Collections.sort(notifications, Notification.CREATED_TIME_SORTER); - templateContext.set("notifications", notifications); - templateContext.set("notificationHash", notifications.hashCode()); - handleRequest(request, templateContext); - } - - protected void handleRequest(@Nonnull FreenetRequest request, @Nonnull TemplateContext templateContext) throws RedirectException { - } - - /** - * {@inheritDoc} - */ - @Override - protected String getRedirectTarget(FreenetRequest request) { - if (requiresLogin() && (getCurrentSoneWithoutCreatingSession(request.getToadletContext()) == null)) { - HTTPRequest httpRequest = request.getHttpRequest(); - String originalUrl = httpRequest.getPath(); - if (httpRequest.hasParameters()) { - StringBuilder requestParameters = new StringBuilder(); - for (String parameterName : httpRequest.getParameterNames()) { - if (requestParameters.length() > 0) { - requestParameters.append("&"); - } - String[] parameterValues = httpRequest.getMultipleParam(parameterName); - for (String parameterValue : parameterValues) { - requestParameters.append(urlEncode(parameterName)).append("=").append(urlEncode(parameterValue)); - } - } - originalUrl += "?" + requestParameters.toString(); - } - return "login.html?target=" + urlEncode(originalUrl); - } - return null; - } - - private static String urlEncode(String value) { - try { - return URLEncoder.encode(value, "UTF-8"); - } catch (UnsupportedEncodingException uee1) { - /* A JVM without UTF-8? I don’t think so. */ - throw new RuntimeException(uee1); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected boolean isFullAccessOnly() { - return webInterface.getCore().getPreferences().isRequireFullAccess(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isEnabled(ToadletContext toadletContext) { - if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) { - return false; - } - if (requiresLogin()) { - return getCurrentSoneWithoutCreatingSession(toadletContext) != null; - } - return true; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/TrustPage.java b/src/main/java/net/pterodactylus/sone/web/TrustPage.java deleted file mode 100644 index 5b0bfbb..0000000 --- a/src/main/java/net/pterodactylus/sone/web/TrustPage.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Sone - TrustPage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import com.google.common.base.Optional; - -import net.pterodactylus.sone.core.Core; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * Page that lets the user trust another Sone. This will assign a configurable - * amount of trust to an identity. - * - * @see Core#trustSone(Sone, Sone) - * @author David ‘Bombe’ Roden - */ -public class TrustPage extends SoneTemplatePage { - - /** - * Creates a new “trust Sone” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public TrustPage(Template template, WebInterface webInterface) { - super("trust.html", template, "Page.Trust.Title", webInterface, true); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); - Sone currentSone = getCurrentSone(request.getToadletContext()); - Optional sone = webInterface.getCore().getSone(identity); - if (sone.isPresent()) { - webInterface.getCore().trustSone(currentSone, sone.get()); - } - throw new RedirectException(returnPage); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java b/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java deleted file mode 100644 index 9cc266e..0000000 --- a/src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Sone - UnbookmarkPage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import java.util.Set; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -import com.google.common.base.Optional; - -/** - * Page that lets the user unbookmark a post. - * - * @author David ‘Bombe’ Roden - */ -public class UnbookmarkPage extends SoneTemplatePage { - - /** - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public UnbookmarkPage(Template template, WebInterface webInterface) { - super("unbookmark.html", template, "Page.Unbookmark.Title", webInterface); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - String id = request.getHttpRequest().getPartAsStringFailsafe("post", 36); - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - Optional post = webInterface.getCore().getPost(id); - if (post.isPresent()) { - webInterface.getCore().unbookmarkPost(post.get()); - } - throw new RedirectException(returnPage); - } - String id = request.getHttpRequest().getParam("post"); - if (id.equals("allNotLoaded")) { - Set posts = webInterface.getCore().getBookmarkedPosts(); - for (Post post : posts) { - if (!post.isLoaded()) { - webInterface.getCore().unbookmarkPost(post); - } - } - throw new RedirectException("bookmarks.html"); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java deleted file mode 100644 index bff4013..0000000 --- a/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Sone - UnfollowSonePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * This page lets the user unfollow another Sone. - * - * @author David ‘Bombe’ Roden - */ -public class UnfollowSonePage extends SoneTemplatePage { - - /** - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public UnfollowSonePage(Template template, WebInterface webInterface) { - super("unfollowSone.html", template, "Page.UnfollowSone.Title", webInterface, true); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - Sone currentSone = getCurrentSone(request.getToadletContext()); - String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 2000); - for (String soneId : soneIds.split("[ ,]+")) { - webInterface.getCore().unfollowSone(currentSone, soneId); - } - throw new RedirectException(returnPage); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/UnlikePage.java b/src/main/java/net/pterodactylus/sone/web/UnlikePage.java deleted file mode 100644 index a17502d..0000000 --- a/src/main/java/net/pterodactylus/sone/web/UnlikePage.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Sone - UnlikePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * Page that lets the user unlike a {@link Post}. - * - * @author David ‘Bombe’ Roden - */ -public class UnlikePage extends SoneTemplatePage { - - /** - * Creates a new “unlike post” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public UnlikePage(Template template, WebInterface webInterface) { - super("unlike.html", template, "Page.Unlike.Title", webInterface, true); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - String type = request.getHttpRequest().getPartAsStringFailsafe("type", 16); - String id = request.getHttpRequest().getPartAsStringFailsafe(type, 36); - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - Sone currentSone = getCurrentSone(request.getToadletContext()); - if ("post".equals(type)) { - currentSone.removeLikedPostId(id); - } else if ("reply".equals(type)) { - currentSone.removeLikedReplyId(id); - } - throw new RedirectException(returnPage); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java b/src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java deleted file mode 100644 index 7c36a88..0000000 --- a/src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Sone - UnlockSonePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -/** - * This page lets the user unlock a {@link Sone} to allow its insertion. - * - * @author David ‘Bombe’ Roden - */ -public class UnlockSonePage extends SoneTemplatePage { - - /** - * Creates a new “unlock Sone” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public UnlockSonePage(Template template, WebInterface webInterface) { - super("unlockSone.html", template, "Page.UnlockSone.Title", webInterface); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); - Sone sone = webInterface.getCore().getLocalSone(soneId); - if (sone != null) { - webInterface.getCore().unlockSone(sone); - } - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - throw new RedirectException(returnPage); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/UntrustPage.java b/src/main/java/net/pterodactylus/sone/web/UntrustPage.java deleted file mode 100644 index cba3635..0000000 --- a/src/main/java/net/pterodactylus/sone/web/UntrustPage.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Sone - UntrustPage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import com.google.common.base.Optional; - -import net.pterodactylus.sone.core.Core; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -/** - * Page that lets the user untrust another Sone. This will remove all trust - * assignments for an identity. - * - * @see Core#untrustSone(Sone, Sone) - * @author David ‘Bombe’ Roden - */ -public class UntrustPage extends SoneTemplatePage { - - /** - * Creates a new “untrust Sone” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public UntrustPage(Template template, WebInterface webInterface) { - super("untrust.html", template, "Page.Untrust.Title", webInterface, true); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); - String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); - Sone currentSone = getCurrentSone(request.getToadletContext()); - Optional sone = webInterface.getCore().getSone(identity); - if (sone.isPresent()) { - webInterface.getCore().untrustSone(currentSone, sone.get()); - } - throw new RedirectException(returnPage); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java b/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java deleted file mode 100644 index 467ceda..0000000 --- a/src/main/java/net/pterodactylus/sone/web/UploadImagePage.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Sone - UploadImagePage.java - Copyright © 2011–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static com.google.common.base.Optional.fromNullable; -import static java.util.logging.Logger.getLogger; - -import java.awt.Image; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; - -import net.pterodactylus.sone.data.Album; -import net.pterodactylus.sone.data.Image.Modifier.ImageTitleMustNotBeEmpty; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.data.TemporaryImage; -import net.pterodactylus.sone.text.TextFilter; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.io.Closer; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; - -import com.google.common.io.ByteStreams; - -import freenet.support.api.Bucket; -import freenet.support.api.HTTPUploadedFile; - -/** - * Page implementation that lets the user upload an image. - * - * @author David ‘Bombe’ Roden - */ -public class UploadImagePage extends SoneTemplatePage { - - private static final Logger logger = getLogger(UploadImagePage.class.getName()); - private static final String UNKNOWN_MIME_TYPE = "application/octet-stream"; - - /** - * Creates a new “upload image” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public UploadImagePage(Template template, WebInterface webInterface) { - super("uploadImage.html", template, "Page.UploadImage.Title", webInterface, true); - } - - // - // SONETEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - if (request.getMethod() == Method.POST) { - Sone currentSone = getCurrentSone(request.getToadletContext()); - String parentId = request.getHttpRequest().getPartAsStringFailsafe("parent", 36); - Album parent = webInterface.getCore().getAlbum(parentId); - if (parent == null) { - throw new RedirectException("noPermission.html"); - } - if (!currentSone.equals(parent.getSone())) { - throw new RedirectException("noPermission.html"); - } - String name = request.getHttpRequest().getPartAsStringFailsafe("title", 200).trim(); - if (name.length() == 0) { - throw new RedirectException("emptyImageTitle.html"); - } - String description = request.getHttpRequest().getPartAsStringFailsafe("description", 4000); - HTTPUploadedFile uploadedFile = request.getHttpRequest().getUploadedFile("image"); - Bucket fileBucket = uploadedFile.getData(); - InputStream imageInputStream = null; - ByteArrayOutputStream imageDataOutputStream = null; - try { - imageInputStream = fileBucket.getInputStream(); - /* TODO - check length */ - imageDataOutputStream = new ByteArrayOutputStream((int) fileBucket.size()); - ByteStreams.copy(imageInputStream, imageDataOutputStream); - } catch (IOException ioe1) { - logger.log(Level.WARNING, "Could not read uploaded image!", ioe1); - return; - } finally { - fileBucket.free(); - Closer.close(imageInputStream); - Closer.close(imageDataOutputStream); - } - byte[] imageData = imageDataOutputStream.toByteArray(); - ByteArrayInputStream imageDataInputStream = null; - Image uploadedImage = null; - try { - imageDataInputStream = new ByteArrayInputStream(imageData); - uploadedImage = ImageIO.read(imageDataInputStream); - if (uploadedImage == null) { - templateContext.set("messages", webInterface.getL10n().getString("Page.UploadImage.Error.InvalidImage")); - return; - } - String mimeType = getMimeType(imageData); - TemporaryImage temporaryImage = webInterface.getCore().createTemporaryImage(mimeType, imageData); - net.pterodactylus.sone.data.Image image = webInterface.getCore().createImage(currentSone, parent, temporaryImage); - image.modify().setTitle(name).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).setWidth(uploadedImage.getWidth(null)).setHeight(uploadedImage.getHeight(null)).update(); - } catch (IOException ioe1) { - logger.log(Level.WARNING, "Could not read uploaded image!", ioe1); - return; - } catch (ImageTitleMustNotBeEmpty itmnbe) { - throw new RedirectException("emptyImageTitle.html"); - } finally { - Closer.close(imageDataInputStream); - Closer.flush(uploadedImage); - } - throw new RedirectException("imageBrowser.html?album=" + parent.getId()); - } - } - - // - // PRIVATE METHODS - // - - /** - * Tries to detect the MIME type of the encoded image. - * - * @param imageData - * The encoded image - * @return The MIME type of the image, or “application/octet-stream” if the - * image type could not be detected - */ - private static String getMimeType(byte[] imageData) { - ByteArrayInputStream imageDataInputStream = new ByteArrayInputStream(imageData); - try { - ImageInputStream imageInputStream = ImageIO.createImageInputStream(imageDataInputStream); - Iterator imageReaders = ImageIO.getImageReaders(imageInputStream); - if (imageReaders.hasNext()) { - return fromNullable(imageReaders.next().getOriginatingProvider().getMIMETypes()) - .or(new String[] { UNKNOWN_MIME_TYPE })[0]; - } - } catch (IOException ioe1) { - logger.log(Level.FINE, "Could not detect MIME type for image.", ioe1); - } - return UNKNOWN_MIME_TYPE; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java b/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java deleted file mode 100644 index 56bac8d..0000000 --- a/src/main/java/net/pterodactylus/sone/web/ViewPostPage.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Sone - ViewPostPage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import java.net.URI; - -import com.google.common.base.Optional; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.template.SoneAccessor; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -/** - * This page lets the user view a post and all its replies. - * - * @author David ‘Bombe’ Roden - */ -public class ViewPostPage extends SoneTemplatePage { - - /** - * Creates a new “view post” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public ViewPostPage(Template template, WebInterface webInterface) { - super("viewPost.html", template, "Page.ViewPost.Title", webInterface, false); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected String getPageTitle(FreenetRequest request) { - String postId = request.getHttpRequest().getParam("post"); - Optional post = webInterface.getCore().getPost(postId); - String title = ""; - if (post.isPresent()) { - title = post.get().getText().substring(0, Math.min(20, post.get().getText().length())) + "…"; - title += " - " + SoneAccessor.getNiceName(post.get().getSone()) + " - "; - } - title += webInterface.getL10n().getString("Page.ViewPost.Title"); - return title; - } - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - String postId = request.getHttpRequest().getParam("post"); - boolean raw = request.getHttpRequest().getParam("raw").equals("true"); - Optional post = webInterface.getCore().getPost(postId); - templateContext.set("post", post.orNull()); - templateContext.set("raw", raw); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLinkExcepted(URI link) { - return true; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java b/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java deleted file mode 100644 index f2c9c91..0000000 --- a/src/main/java/net/pterodactylus/sone/web/ViewSonePage.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Sone - ViewSonePage.java - Copyright © 2010–2016 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.web; - -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.PostReply; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.template.SoneAccessor; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.util.collection.Pagination; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -import com.google.common.base.Optional; - -/** - * Lets the user browser another Sone. - * - * @author David ‘Bombe’ Roden - */ -public class ViewSonePage extends SoneTemplatePage { - - /** - * Creates a new “view Sone” page. - * - * @param template - * The template to render - * @param webInterface - * The Sone web interface - */ - public ViewSonePage(Template template, WebInterface webInterface) { - super("viewSone.html", template, webInterface, false); - } - - // - // TEMPLATEPAGE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected String getPageTitle(FreenetRequest request) { - String soneId = request.getHttpRequest().getParam("sone"); - Optional sone = webInterface.getCore().getSone(soneId); - if (sone.isPresent()) { - String soneName = SoneAccessor.getNiceName(sone.get()); - return soneName + " - " + webInterface.getL10n().getString("Page.ViewSone.Title"); - } - return webInterface.getL10n().getString("Page.ViewSone.Page.TitleWithoutSone"); - } - - /** - * {@inheritDoc} - */ - @Override - protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { - String soneId = request.getHttpRequest().getParam("sone"); - Optional sone = webInterface.getCore().getSone(soneId); - templateContext.set("sone", sone.orNull()); - templateContext.set("soneId", soneId); - if (!sone.isPresent()) { - return; - } - List sonePosts = sone.get().getPosts(); - sonePosts.addAll(webInterface.getCore().getDirectedPosts(sone.get().getId())); - Collections.sort(sonePosts, Post.NEWEST_FIRST); - Pagination postPagination = new Pagination(sonePosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("postPage"), 0)); - templateContext.set("postPagination", postPagination); - templateContext.set("posts", postPagination.getItems()); - Set replies = sone.get().getReplies(); - final Map> repliedPosts = new HashMap>(); - for (PostReply reply : replies) { - Optional post = reply.getPost(); - if (!post.isPresent() || repliedPosts.containsKey(post.get()) || sone.get().equals(post.get().getSone()) || (sone.get().getId().equals(post.get().getRecipientId().orNull()))) { - continue; - } - repliedPosts.put(post.get(), webInterface.getCore().getReplies(post.get().getId())); - } - List posts = new ArrayList(repliedPosts.keySet()); - Collections.sort(posts, new Comparator() { - - @Override - public int compare(Post leftPost, Post rightPost) { - return (int) Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, repliedPosts.get(rightPost).get(0).getTime() - repliedPosts.get(leftPost).get(0).getTime())); - } - - }); - - Pagination repliedPostPagination = new Pagination(posts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("repliedPostPage"), 0)); - templateContext.set("repliedPostPagination", repliedPostPagination); - templateContext.set("repliedPosts", repliedPostPagination.getItems()); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLinkExcepted(URI link) { - return true; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 6ce38d4..ee309df 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -140,6 +140,49 @@ import net.pterodactylus.sone.web.ajax.UntrustAjaxPage; import net.pterodactylus.sone.web.page.FreenetRequest; import net.pterodactylus.sone.web.page.PageToadlet; import net.pterodactylus.sone.web.page.PageToadletFactory; +import net.pterodactylus.sone.web.pages.AboutPage; +import net.pterodactylus.sone.web.pages.BookmarkPage; +import net.pterodactylus.sone.web.pages.BookmarksPage; +import net.pterodactylus.sone.web.pages.CreateAlbumPage; +import net.pterodactylus.sone.web.pages.CreatePostPage; +import net.pterodactylus.sone.web.pages.CreateReplyPage; +import net.pterodactylus.sone.web.pages.CreateSonePage; +import net.pterodactylus.sone.web.pages.DeleteAlbumPage; +import net.pterodactylus.sone.web.pages.DeleteImagePage; +import net.pterodactylus.sone.web.pages.DeletePostPage; +import net.pterodactylus.sone.web.pages.DeleteProfileFieldPage; +import net.pterodactylus.sone.web.pages.DeleteReplyPage; +import net.pterodactylus.sone.web.pages.DeleteSonePage; +import net.pterodactylus.sone.web.pages.DismissNotificationPage; +import net.pterodactylus.sone.web.pages.DistrustPage; +import net.pterodactylus.sone.web.pages.EditAlbumPage; +import net.pterodactylus.sone.web.pages.EditImagePage; +import net.pterodactylus.sone.web.pages.EditProfileFieldPage; +import net.pterodactylus.sone.web.pages.EditProfilePage; +import net.pterodactylus.sone.web.pages.FollowSonePage; +import net.pterodactylus.sone.web.pages.GetImagePage; +import net.pterodactylus.sone.web.pages.ImageBrowserPage; +import net.pterodactylus.sone.web.pages.IndexPage; +import net.pterodactylus.sone.web.pages.KnownSonesPage; +import net.pterodactylus.sone.web.pages.LikePage; +import net.pterodactylus.sone.web.pages.LockSonePage; +import net.pterodactylus.sone.web.pages.LoginPage; +import net.pterodactylus.sone.web.pages.LogoutPage; +import net.pterodactylus.sone.web.pages.MarkAsKnownPage; +import net.pterodactylus.sone.web.pages.NewPage; +import net.pterodactylus.sone.web.pages.OptionsPage; +import net.pterodactylus.sone.web.pages.RescuePage; +import net.pterodactylus.sone.web.pages.SearchPage; +import net.pterodactylus.sone.web.pages.SoneTemplatePage; +import net.pterodactylus.sone.web.pages.TrustPage; +import net.pterodactylus.sone.web.pages.UnbookmarkPage; +import net.pterodactylus.sone.web.pages.UnfollowSonePage; +import net.pterodactylus.sone.web.pages.UnlikePage; +import net.pterodactylus.sone.web.pages.UnlockSonePage; +import net.pterodactylus.sone.web.pages.UntrustPage; +import net.pterodactylus.sone.web.pages.UploadImagePage; +import net.pterodactylus.sone.web.pages.ViewPostPage; +import net.pterodactylus.sone.web.pages.ViewSonePage; import net.pterodactylus.util.notify.Notification; import net.pterodactylus.util.notify.NotificationManager; import net.pterodactylus.util.notify.TemplateNotification; diff --git a/src/main/java/net/pterodactylus/sone/web/pages/EditImagePage.java b/src/main/java/net/pterodactylus/sone/web/pages/EditImagePage.java new file mode 100644 index 0000000..34f404e --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/EditImagePage.java @@ -0,0 +1,83 @@ +/* + * Sone - EditImagePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import net.pterodactylus.sone.data.Image; +import net.pterodactylus.sone.text.TextFilter; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user edit title and description of an {@link Image}. + * + * @author David ‘Bombe’ Roden + */ +public class EditImagePage extends SoneTemplatePage { + + /** + * Creates a new “edit image” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public EditImagePage(Template template, WebInterface webInterface) { + super("editImage.html", template, "Page.EditImage.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + String imageId = request.getHttpRequest().getPartAsStringFailsafe("image", 36); + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + Image image = webInterface.getCore().getImage(imageId, false); + if (image == null) { + throw new RedirectException("invalid.html"); + } + if (!image.getSone().isLocal()) { + throw new RedirectException("noPermission.html"); + } + if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveLeft", 4))) { + image.getAlbum().moveImageUp(image); + } else if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveRight", 4))) { + image.getAlbum().moveImageDown(image); + } else { + String title = request.getHttpRequest().getPartAsStringFailsafe("title", 100).trim(); + String description = request.getHttpRequest().getPartAsStringFailsafe("description", 1024).trim(); + if (title.length() == 0) { + throw new RedirectException("emptyImageTitle.html"); + } + image.modify().setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).update(); + } + webInterface.getCore().touchConfiguration(); + throw new RedirectException(returnPage); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/EditProfileFieldPage.java b/src/main/java/net/pterodactylus/sone/web/pages/EditProfileFieldPage.java new file mode 100644 index 0000000..2bfdddf --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/EditProfileFieldPage.java @@ -0,0 +1,94 @@ +/* + * Sone - EditProfileFieldPage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user edit the name of a profile field. + * + * @author David ‘Bombe’ Roden + */ +public class EditProfileFieldPage extends SoneTemplatePage { + + /** + * Creates a new “edit profile field” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public EditProfileFieldPage(Template template, WebInterface webInterface) { + super("editProfileField.html", template, "Page.EditProfileField.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + Sone currentSone = getCurrentSone(request.getToadletContext()); + Profile profile = currentSone.getProfile(); + + /* get parameters from request. */ + String fieldId = request.getHttpRequest().getParam("field"); + Field field = profile.getFieldById(fieldId); + if (field == null) { + throw new RedirectException("invalid.html"); + } + + /* process the POST request. */ + if (request.getMethod() == Method.POST) { + if (request.getHttpRequest().getPartAsStringFailsafe("cancel", 4).equals("true")) { + throw new RedirectException("editProfile.html#profile-fields"); + } + fieldId = request.getHttpRequest().getPartAsStringFailsafe("field", 36); + field = profile.getFieldById(fieldId); + if (field == null) { + throw new RedirectException("invalid.html"); + } + String name = request.getHttpRequest().getPartAsStringFailsafe("name", 256); + Field existingField = profile.getFieldByName(name); + if ((existingField != null) && (!field.equals(existingField))) { + templateContext.set("duplicateFieldName", true); + } else { + if (!name.equals(field.getName())) { + field.setName(name); + currentSone.setProfile(profile); + } + throw new RedirectException("editProfile.html#profile-fields"); + } + } + + /* store current values in template. */ + templateContext.set("field", field); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/EditProfilePage.java b/src/main/java/net/pterodactylus/sone/web/pages/EditProfilePage.java new file mode 100644 index 0000000..b17a22e --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/EditProfilePage.java @@ -0,0 +1,172 @@ +/* + * Sone - EditProfilePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static net.pterodactylus.sone.text.TextFilter.filter; +import static net.pterodactylus.sone.utils.NumberParsers.parseInt; + +import java.util.List; + +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.DuplicateField; +import net.pterodactylus.sone.data.Profile.Field; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; +import freenet.clients.http.ToadletContext; + +/** + * This page lets the user edit her profile. + * + * @author David ‘Bombe’ Roden + */ +public class EditProfilePage extends SoneTemplatePage { + + /** + * Creates a new “edit profile” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public EditProfilePage(Template template, WebInterface webInterface) { + super("editProfile.html", template, "Page.EditProfile.Title", webInterface, true); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + ToadletContext toadletContenxt = request.getToadletContext(); + Sone currentSone = getCurrentSone(toadletContenxt); + Profile profile = currentSone.getProfile(); + String firstName = profile.getFirstName(); + String middleName = profile.getMiddleName(); + String lastName = profile.getLastName(); + Integer birthDay = profile.getBirthDay(); + Integer birthMonth = profile.getBirthMonth(); + Integer birthYear = profile.getBirthYear(); + String avatarId = profile.getAvatar(); + List fields = profile.getFields(); + if (request.getMethod() == Method.POST) { + if (request.getHttpRequest().getPartAsStringFailsafe("save-profile", 4).equals("true")) { + firstName = request.getHttpRequest().getPartAsStringFailsafe("first-name", 256).trim(); + middleName = request.getHttpRequest().getPartAsStringFailsafe("middle-name", 256).trim(); + lastName = request.getHttpRequest().getPartAsStringFailsafe("last-name", 256).trim(); + birthDay = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim(), null); + birthMonth = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim(), null); + birthYear = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim(), null); + avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatarId", 36); + profile.setFirstName(firstName.length() > 0 ? firstName : null); + profile.setMiddleName(middleName.length() > 0 ? middleName : null); + profile.setLastName(lastName.length() > 0 ? lastName : null); + profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear); + profile.setAvatar(webInterface.getCore().getImage(avatarId, false)); + for (Field field : fields) { + String value = request.getHttpRequest().getPartAsStringFailsafe("field-" + field.getId(), 400); + String filteredValue = filter(request.getHttpRequest().getHeader("Host"), value); + field.setValue(filteredValue); + } + currentSone.setProfile(profile); + webInterface.getCore().touchConfiguration(); + throw new RedirectException("editProfile.html"); + } else if (request.getHttpRequest().getPartAsStringFailsafe("add-field", 4).equals("true")) { + String fieldName = request.getHttpRequest().getPartAsStringFailsafe("field-name", 256).trim(); + try { + profile.addField(fieldName); + currentSone.setProfile(profile); + webInterface.getCore().touchConfiguration(); + throw new RedirectException("editProfile.html#profile-fields"); + } catch (DuplicateField df1) { + templateContext.set("fieldName", fieldName); + templateContext.set("duplicateFieldName", true); + } + } else { + String id = getFieldId(request, "delete-field-"); + if (id != null) { + throw new RedirectException("deleteProfileField.html?field=" + id); + } + id = getFieldId(request, "move-up-field-"); + if (id != null) { + Field field = profile.getFieldById(id); + if (field == null) { + throw new RedirectException("invalid.html"); + } + profile.moveFieldUp(field); + currentSone.setProfile(profile); + throw new RedirectException("editProfile.html#profile-fields"); + } + id = getFieldId(request, "move-down-field-"); + if (id != null) { + Field field = profile.getFieldById(id); + if (field == null) { + throw new RedirectException("invalid.html"); + } + profile.moveFieldDown(field); + currentSone.setProfile(profile); + throw new RedirectException("editProfile.html#profile-fields"); + } + id = getFieldId(request, "edit-field-"); + if (id != null) { + throw new RedirectException("editProfileField.html?field=" + id); + } + } + } + templateContext.set("firstName", firstName); + templateContext.set("middleName", middleName); + templateContext.set("lastName", lastName); + templateContext.set("birthDay", birthDay); + templateContext.set("birthMonth", birthMonth); + templateContext.set("birthYear", birthYear); + templateContext.set("avatarId", avatarId); + templateContext.set("fields", fields); + } + + // + // PRIVATE METHODS + // + + /** + * Searches for a part whose names starts with the given {@code String} and + * extracts the ID from the located name. + * + * @param request + * The request to get the parts from + * @param partNameStart + * The start of the name of the requested part + * @return The parsed ID, or {@code null} if there was no part matching the + * given string + */ + private static String getFieldId(FreenetRequest request, String partNameStart) { + for (String partName : request.getHttpRequest().getParts()) { + if (partName.startsWith(partNameStart)) { + return partName.substring(partNameStart.length()); + } + } + return null; + } +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/FollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/pages/FollowSonePage.java new file mode 100644 index 0000000..9bfb790 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/FollowSonePage.java @@ -0,0 +1,70 @@ +/* + * Sone - FollowSonePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import com.google.common.base.Optional; + +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * This page lets the user follow another Sone. + * + * @author David ‘Bombe’ Roden + */ +public class FollowSonePage extends SoneTemplatePage { + + /** + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public FollowSonePage(Template template, WebInterface webInterface) { + super("followSone.html", template, "Page.FollowSone.Title", webInterface, true); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + Sone currentSone = getCurrentSone(request.getToadletContext()); + String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 1200); + for (String soneId : soneIds.split("[ ,]+")) { + Optional sone = webInterface.getCore().getSone(soneId); + if (sone.isPresent()) { + webInterface.getCore().followSone(currentSone, soneId); + webInterface.getCore().markSoneKnown(sone.get()); + } + } + throw new RedirectException(returnPage); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/GetImagePage.java b/src/main/java/net/pterodactylus/sone/web/pages/GetImagePage.java new file mode 100644 index 0000000..bcf0eda --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/GetImagePage.java @@ -0,0 +1,87 @@ +/* + * Sone - GetImagePage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import java.io.IOException; +import java.net.URI; + +import net.pterodactylus.sone.data.TemporaryImage; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetPage; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.web.Response; + +/** + * Page that delivers a {@link TemporaryImage} to the browser. + * + * @author David ‘Bombe’ Roden + */ +public class GetImagePage implements FreenetPage { + + /** The Sone web interface. */ + private final WebInterface webInterface; + + /** + * Creates a new “get image” page. + * + * @param webInterface + * The Sone web interface + */ + public GetImagePage(WebInterface webInterface) { + this.webInterface = webInterface; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPath() { + return "getImage.html"; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrefixPage() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public Response handleRequest(FreenetRequest request, Response response) throws IOException { + String imageId = request.getHttpRequest().getParam("image"); + TemporaryImage temporaryImage = webInterface.getCore().getTemporaryImage(imageId); + if (temporaryImage == null) { + return response.setStatusCode(404).setStatusText("Not found.").setContentType("text/html; charset=utf-8"); + } + String contentType= temporaryImage.getMimeType(); + return response.setStatusCode(200).setStatusText("OK").setContentType(contentType).addHeader("Content-Disposition", "attachment; filename=" + temporaryImage.getId() + "." + contentType.substring(contentType.lastIndexOf('/') + 1)).write(temporaryImage.getImageData()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return false; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/ImageBrowserPage.java b/src/main/java/net/pterodactylus/sone/web/pages/ImageBrowserPage.java new file mode 100644 index 0000000..2e53761 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/ImageBrowserPage.java @@ -0,0 +1,118 @@ +/* + * Sone - ImageBrowserPage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static com.google.common.collect.FluentIterable.from; +import static net.pterodactylus.sone.data.Album.FLATTENER; +import static net.pterodactylus.sone.data.Album.NOT_EMPTY; +import static net.pterodactylus.sone.data.Album.TITLE_COMPARATOR; +import static net.pterodactylus.sone.utils.NumberParsers.parseInt; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.google.common.base.Optional; + +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Image; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.collection.Pagination; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +/** + * The image browser page is the entry page for the image management. + * + * @author David ‘Bombe’ Roden + */ +public class ImageBrowserPage extends SoneTemplatePage { + + /** + * Creates a new image browser page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public ImageBrowserPage(Template template, WebInterface webInterface) { + super("imageBrowser.html", template, "Page.ImageBrowser.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + String albumId = request.getHttpRequest().getParam("album", null); + if (albumId != null) { + Album album = webInterface.getCore().getAlbum(albumId); + templateContext.set("albumRequested", true); + templateContext.set("album", album); + templateContext.set("page", request.getHttpRequest().getParam("page")); + return; + } + String imageId = request.getHttpRequest().getParam("image", null); + if (imageId != null) { + Image image = webInterface.getCore().getImage(imageId, false); + templateContext.set("imageRequested", true); + templateContext.set("image", image); + return; + } + String soneId = request.getHttpRequest().getParam("sone", null); + if (soneId != null) { + Optional sone = webInterface.getCore().getSone(soneId); + templateContext.set("soneRequested", true); + templateContext.set("sone", sone.orNull()); + return; + } + String mode = request.getHttpRequest().getParam("mode", null); + if ("gallery".equals(mode)) { + templateContext.set("galleryRequested", true); + List albums = new ArrayList(); + for (Sone sone : webInterface.getCore().getSones()) { + albums.addAll(from(sone.getRootAlbum().getAlbums()).transformAndConcat(FLATTENER).filter(NOT_EMPTY).toList()); + } + Collections.sort(albums, TITLE_COMPARATOR); + Pagination albumPagination = new Pagination(albums, 12).setPage(parseInt(request.getHttpRequest().getParam("page"), 0)); + templateContext.set("albumPagination", albumPagination); + templateContext.set("albums", albumPagination.getItems()); + return; + } + Sone sone = getCurrentSoneWithoutCreatingSession(request.getToadletContext()); + templateContext.set("soneRequested", true); + templateContext.set("sone", sone); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return true; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/IndexPage.java b/src/main/java/net/pterodactylus/sone/web/pages/IndexPage.java new file mode 100644 index 0000000..5946a57 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/IndexPage.java @@ -0,0 +1,88 @@ +/* + * Sone - IndexPage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static net.pterodactylus.sone.utils.NumberParsers.parseInt; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.notify.PostVisibilityFilter; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.collection.Pagination; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +import com.google.common.base.Optional; +import com.google.common.collect.Collections2; + +/** + * The index page shows the main page of Sone. This page will contain the posts + * of all friends of the current user. + * + * @author David ‘Bombe’ Roden + */ +public class IndexPage extends SoneTemplatePage { + + private final PostVisibilityFilter postVisibilityFilter; + + public IndexPage(Template template, WebInterface webInterface, PostVisibilityFilter postVisibilityFilter) { + super("index.html", template, "Page.Index.Title", webInterface, true); + this.postVisibilityFilter = postVisibilityFilter; + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + final Sone currentSone = getCurrentSone(request.getToadletContext()); + Collection allPosts = new ArrayList(); + allPosts.addAll(currentSone.getPosts()); + for (String friendSoneId : currentSone.getFriends()) { + Optional friendSone = webInterface.getCore().getSone(friendSoneId); + if (!friendSone.isPresent()) { + continue; + } + allPosts.addAll(friendSone.get().getPosts()); + } + for (Sone sone : webInterface.getCore().getSones()) { + for (Post post : sone.getPosts()) { + if (currentSone.equals(post.getRecipient().orNull()) && !allPosts.contains(post)) { + allPosts.add(post); + } + } + } + allPosts = Collections2.filter(allPosts, postVisibilityFilter.isVisible(currentSone)); + List sortedPosts = new ArrayList(allPosts); + Collections.sort(sortedPosts, Post.NEWEST_FIRST); + Pagination pagination = new Pagination(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0)); + templateContext.set("pagination", pagination); + templateContext.set("posts", pagination.getItems()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/KnownSonesPage.java b/src/main/java/net/pterodactylus/sone/web/pages/KnownSonesPage.java new file mode 100644 index 0000000..5aef1f9 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/KnownSonesPage.java @@ -0,0 +1,152 @@ +/* + * Sone - KnownSonesPage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static net.pterodactylus.sone.utils.NumberParsers.parseInt; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.collection.Pagination; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Collections2; +import com.google.common.collect.Ordering; + +/** + * This page shows all known Sones. + * + * @author David ‘Bombe’ Roden + */ +public class KnownSonesPage extends SoneTemplatePage { + + private static final String defaultSortField = "activity"; + private static final String defaultSortOrder = "desc"; + + /** + * Creates a “known Sones” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public KnownSonesPage(Template template, WebInterface webInterface) { + super("knownSones.html", template, "Page.KnownSones.Title", webInterface, false); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + String sortField = request.getHttpRequest().getParam("sort", defaultSortField); + String sortOrder = request.getHttpRequest().getParam("order", defaultSortOrder); + String filter = request.getHttpRequest().getParam("filter"); + templateContext.set("sort", sortField); + templateContext.set("order", sortOrder); + templateContext.set("filter", filter); + final Sone currentSone = getCurrentSoneWithoutCreatingSession(request.getToadletContext()); + Collection knownSones = Collections2.filter(webInterface.getCore().getSones(), Sone.EMPTY_SONE_FILTER); + if ((currentSone != null) && "followed".equals(filter)) { + knownSones = Collections2.filter(knownSones, new Predicate() { + + @Override + public boolean apply(Sone sone) { + return currentSone.hasFriend(sone.getId()); + } + }); + } else if ((currentSone != null) && "not-followed".equals(filter)) { + knownSones = Collections2.filter(knownSones, new Predicate() { + + @Override + public boolean apply(Sone sone) { + return !currentSone.hasFriend(sone.getId()); + } + }); + } else if ("new".equals(filter)) { + knownSones = Collections2.filter(knownSones, new Predicate() { + + /** + * {@inheritDoc} + */ + @Override + public boolean apply(Sone sone) { + return !sone.isKnown(); + } + }); + } else if ("not-new".equals(filter)) { + knownSones = Collections2.filter(knownSones, new Predicate() { + + /** + * {@inheritDoc} + */ + @Override + public boolean apply(Sone sone) { + return sone.isKnown(); + } + }); + } else if ("own".equals(filter)) { + knownSones = Collections2.filter(knownSones, Sone.LOCAL_SONE_FILTER); + } else if ("not-own".equals(filter)) { + knownSones = Collections2.filter(knownSones, Predicates.not(Sone.LOCAL_SONE_FILTER)); + } + List sortedSones = new ArrayList(knownSones); + if ("activity".equals(sortField)) { + if ("asc".equals(sortOrder)) { + Collections.sort(sortedSones, Ordering.from(Sone.LAST_ACTIVITY_COMPARATOR).reverse()); + } else { + Collections.sort(sortedSones, Sone.LAST_ACTIVITY_COMPARATOR); + } + } else if ("posts".equals(sortField)) { + if ("asc".equals(sortOrder)) { + Collections.sort(sortedSones, Ordering.from(Sone.POST_COUNT_COMPARATOR).reverse()); + } else { + Collections.sort(sortedSones, Sone.POST_COUNT_COMPARATOR); + } + } else if ("images".equals(sortField)) { + if ("asc".equals(sortOrder)) { + Collections.sort(sortedSones, Ordering.from(Sone.IMAGE_COUNT_COMPARATOR).reverse()); + } else { + Collections.sort(sortedSones, Sone.IMAGE_COUNT_COMPARATOR); + } + } else { + if ("desc".equals(sortOrder)) { + Collections.sort(sortedSones, Ordering.from(Sone.NICE_NAME_COMPARATOR).reverse()); + } else { + Collections.sort(sortedSones, Sone.NICE_NAME_COMPARATOR); + } + } + Pagination sonePagination = new Pagination(sortedSones, 25).setPage(parseInt(request.getHttpRequest().getParam("page"), 0)); + templateContext.set("pagination", sonePagination); + templateContext.set("knownSones", sonePagination.getItems()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/LikePage.java b/src/main/java/net/pterodactylus/sone/web/pages/LikePage.java new file mode 100644 index 0000000..529b620 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/LikePage.java @@ -0,0 +1,70 @@ +/* + * Sone - LikePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user like a {@link Post}. + * + * @author David ‘Bombe’ Roden + */ +public class LikePage extends SoneTemplatePage { + + /** + * Creates a new “like post” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public LikePage(Template template, WebInterface webInterface) { + super("like.html", template, "Page.Like.Title", webInterface, true); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + String type = request.getHttpRequest().getPartAsStringFailsafe("type", 16); + String id = request.getHttpRequest().getPartAsStringFailsafe(type, 36); + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + Sone currentSone = getCurrentSone(request.getToadletContext()); + if ("post".equals(type)) { + currentSone.addLikedPostId(id); + } else if ("reply".equals(type)) { + currentSone.addLikedReplyId(id); + } + throw new RedirectException(returnPage); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/LockSonePage.java b/src/main/java/net/pterodactylus/sone/web/pages/LockSonePage.java new file mode 100644 index 0000000..8300711 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/LockSonePage.java @@ -0,0 +1,64 @@ +/* + * Sone - LockSonePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +/** + * This page lets the user lock a {@link Sone} to prevent it from being + * inserted. + * + * @author David ‘Bombe’ Roden + */ +public class LockSonePage extends SoneTemplatePage { + + /** + * Creates a new “lock Sone” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public LockSonePage(Template template, WebInterface webInterface) { + super("lockSone.html", template, "Page.LockSone.Title", webInterface); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); + Sone sone = webInterface.getCore().getLocalSone(soneId); + if (sone != null) { + webInterface.getCore().lockSone(sone); + } + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + throw new RedirectException(returnPage); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/LogoutPage.java b/src/main/java/net/pterodactylus/sone/web/pages/LogoutPage.java new file mode 100644 index 0000000..1249086 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/LogoutPage.java @@ -0,0 +1,67 @@ +/* + * Sone - LogoutPage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import freenet.clients.http.ToadletContext; + +/** + * Logs a user out. + * + * @author David ‘Bombe’ Roden + */ +public class LogoutPage extends SoneTemplatePage { + + /** + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public LogoutPage(Template template, WebInterface webInterface) { + super("logout.html", template, "Page.Logout.Title", webInterface, true); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + setCurrentSone(request.getToadletContext(), null); + throw new RedirectException("index.html"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled(ToadletContext toadletContext) { + if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) { + return false; + } + return (getCurrentSoneWithoutCreatingSession(toadletContext) != null) && (webInterface.getCore().getLocalSones().size() != 1); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/MarkAsKnownPage.java b/src/main/java/net/pterodactylus/sone/web/pages/MarkAsKnownPage.java new file mode 100644 index 0000000..4eb7a95 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/MarkAsKnownPage.java @@ -0,0 +1,93 @@ +/* + * Sone - MarkAsKnownPage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import java.util.StringTokenizer; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Reply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +import com.google.common.base.Optional; + +/** + * Page that lets the user mark a number of {@link Sone}s, {@link Post}s, or + * {@link Reply Replie}s as known. + * + * @author David ‘Bombe’ Roden + */ +public class MarkAsKnownPage extends SoneTemplatePage { + + /** + * Creates a new “mark as known” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public MarkAsKnownPage(Template template, WebInterface webInterface) { + super("markAsKnown.html", template, "Page.MarkAsKnown.Title", webInterface); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + String type = request.getHttpRequest().getPartAsStringFailsafe("type", 5); + if (!type.equals("sone") && !type.equals("post") && !type.equals("reply")) { + throw new RedirectException("invalid.html"); + } + String ids = request.getHttpRequest().getPartAsStringFailsafe("id", 65536); + for (StringTokenizer idTokenizer = new StringTokenizer(ids); idTokenizer.hasMoreTokens();) { + String id = idTokenizer.nextToken(); + if (type.equals("post")) { + Optional post = webInterface.getCore().getPost(id); + if (!post.isPresent()) { + continue; + } + webInterface.getCore().markPostKnown(post.get()); + } else if (type.equals("reply")) { + Optional reply = webInterface.getCore().getPostReply(id); + if (!reply.isPresent()) { + continue; + } + webInterface.getCore().markReplyKnown(reply.get()); + } else { + Optional sone = webInterface.getCore().getSone(id); + if (!sone.isPresent()) { + continue; + } + webInterface.getCore().markSoneKnown(sone.get()); + } + } + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + throw new RedirectException(returnPage); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/NewPage.java b/src/main/java/net/pterodactylus/sone/web/pages/NewPage.java new file mode 100644 index 0000000..411f40f --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/NewPage.java @@ -0,0 +1,83 @@ +/* + * Sone - NewPage.java - Copyright © 2013–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static net.pterodactylus.sone.utils.NumberParsers.parseInt; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.notify.PostVisibilityFilter; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.collection.Pagination; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +/** + * Page that displays all new posts and replies. The posts are filtered using + * {@link PostVisibilityFilter#isPostVisible(Sone, Post)} and sorted by time. + * + * @author David ‘Bombe’ Roden + */ +public class NewPage extends SoneTemplatePage { + + /** + * Creates a new “new posts and replies” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public NewPage(Template template, WebInterface webInterface) { + super("new.html", template, "Page.New.Title", webInterface); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + /* collect new elements from notifications. */ + Set posts = new HashSet(webInterface.getNewPosts(getCurrentSoneWithoutCreatingSession(request.getToadletContext()))); + for (PostReply reply : webInterface.getNewReplies(getCurrentSoneWithoutCreatingSession(request.getToadletContext()))) { + posts.add(reply.getPost().get()); + } + + /* filter and sort them. */ + List sortedPosts = new ArrayList<>(posts); + Collections.sort(sortedPosts, Post.NEWEST_FIRST); + + /* paginate them. */ + Pagination pagination = new Pagination<>(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0)); + templateContext.set("pagination", pagination); + templateContext.set("posts", pagination.getItems()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/OptionsPage.java b/src/main/java/net/pterodactylus/sone/web/pages/OptionsPage.java new file mode 100644 index 0000000..7b65c33 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/OptionsPage.java @@ -0,0 +1,166 @@ +/* + * Sone - OptionsPage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static net.pterodactylus.sone.utils.NumberParsers.parseInt; + +import java.util.ArrayList; +import java.util.List; + +import net.pterodactylus.sone.core.Preferences; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent; +import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * This page lets the user edit the options of the Sone plugin. + * + * @author David ‘Bombe’ Roden + */ +public class OptionsPage extends SoneTemplatePage { + + /** + * Creates a new options page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public OptionsPage(Template template, WebInterface webInterface) { + super("options.html", template, "Page.Options.Title", webInterface, false); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + Preferences preferences = webInterface.getCore().getPreferences(); + Sone currentSone = webInterface.getCurrentSoneWithoutCreatingSession(request.getToadletContext()); + if (request.getMethod() == Method.POST) { + List fieldErrors = new ArrayList(); + if (currentSone != null) { + boolean autoFollow = request.getHttpRequest().isPartSet("auto-follow"); + currentSone.getOptions().setAutoFollow(autoFollow); + boolean enableSoneInsertNotifications = request.getHttpRequest().isPartSet("enable-sone-insert-notifications"); + currentSone.getOptions().setSoneInsertNotificationEnabled(enableSoneInsertNotifications); + boolean showNotificationNewSones = request.getHttpRequest().isPartSet("show-notification-new-sones"); + currentSone.getOptions().setShowNewSoneNotifications(showNotificationNewSones); + boolean showNotificationNewPosts = request.getHttpRequest().isPartSet("show-notification-new-posts"); + currentSone.getOptions().setShowNewPostNotifications(showNotificationNewPosts); + boolean showNotificationNewReplies = request.getHttpRequest().isPartSet("show-notification-new-replies"); + currentSone.getOptions().setShowNewReplyNotifications(showNotificationNewReplies); + String showCustomAvatars = request.getHttpRequest().getPartAsStringFailsafe("show-custom-avatars", 32); + currentSone.getOptions().setShowCustomAvatars(LoadExternalContent.valueOf(showCustomAvatars)); + String loadLinkedImages = request.getHttpRequest().getPartAsStringFailsafe("load-linked-images", 32); + currentSone.getOptions().setLoadLinkedImages(LoadExternalContent.valueOf(loadLinkedImages)); + webInterface.getCore().touchConfiguration(); + } + Integer insertionDelay = parseInt(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16), null); + if (!preferences.validateInsertionDelay(insertionDelay)) { + fieldErrors.add("insertion-delay"); + } else { + preferences.setInsertionDelay(insertionDelay); + } + Integer postsPerPage = parseInt(request.getHttpRequest().getPartAsStringFailsafe("posts-per-page", 4), null); + if (!preferences.validatePostsPerPage(postsPerPage)) { + fieldErrors.add("posts-per-page"); + } else { + preferences.setPostsPerPage(postsPerPage); + } + Integer imagesPerPage = parseInt(request.getHttpRequest().getPartAsStringFailsafe("images-per-page", 4), null); + if (!preferences.validateImagesPerPage(imagesPerPage)) { + fieldErrors.add("images-per-page"); + } else { + preferences.setImagesPerPage(imagesPerPage); + } + Integer charactersPerPost = parseInt(request.getHttpRequest().getPartAsStringFailsafe("characters-per-post", 10), null); + if (!preferences.validateCharactersPerPost(charactersPerPost)) { + fieldErrors.add("characters-per-post"); + } else { + preferences.setCharactersPerPost(charactersPerPost); + } + Integer postCutOffLength = parseInt(request.getHttpRequest().getPartAsStringFailsafe("post-cut-off-length", 10), null); + if (!preferences.validatePostCutOffLength(postCutOffLength)) { + fieldErrors.add("post-cut-off-length"); + } else { + preferences.setPostCutOffLength(postCutOffLength); + } + boolean requireFullAccess = request.getHttpRequest().isPartSet("require-full-access"); + preferences.setRequireFullAccess(requireFullAccess); + Integer positiveTrust = parseInt(request.getHttpRequest().getPartAsStringFailsafe("positive-trust", 3), null); + if (!preferences.validatePositiveTrust(positiveTrust)) { + fieldErrors.add("positive-trust"); + } else { + preferences.setPositiveTrust(positiveTrust); + } + Integer negativeTrust = parseInt(request.getHttpRequest().getPartAsStringFailsafe("negative-trust", 4), null); + if (!preferences.validateNegativeTrust(negativeTrust)) { + fieldErrors.add("negative-trust"); + } else { + preferences.setNegativeTrust(negativeTrust); + } + String trustComment = request.getHttpRequest().getPartAsStringFailsafe("trust-comment", 256); + if (trustComment.trim().length() == 0) { + trustComment = null; + } + preferences.setTrustComment(trustComment); + boolean fcpInterfaceActive = request.getHttpRequest().isPartSet("fcp-interface-active"); + preferences.setFcpInterfaceActive(fcpInterfaceActive); + Integer fcpFullAccessRequiredInteger = parseInt(request.getHttpRequest().getPartAsStringFailsafe("fcp-full-access-required", 1), preferences.getFcpFullAccessRequired().ordinal()); + FullAccessRequired fcpFullAccessRequired = FullAccessRequired.values()[fcpFullAccessRequiredInteger]; + preferences.setFcpFullAccessRequired(fcpFullAccessRequired); + webInterface.getCore().touchConfiguration(); + if (fieldErrors.isEmpty()) { + throw new RedirectException(getPath()); + } + templateContext.set("fieldErrors", fieldErrors); + } + if (currentSone != null) { + templateContext.set("auto-follow", currentSone.getOptions().isAutoFollow()); + templateContext.set("enable-sone-insert-notifications", currentSone.getOptions().isSoneInsertNotificationEnabled()); + templateContext.set("show-notification-new-sones", currentSone.getOptions().isShowNewSoneNotifications()); + templateContext.set("show-notification-new-posts", currentSone.getOptions().isShowNewPostNotifications()); + templateContext.set("show-notification-new-replies", currentSone.getOptions().isShowNewReplyNotifications()); + templateContext.set("show-custom-avatars", currentSone.getOptions().getShowCustomAvatars().name()); + templateContext.set("load-linked-images", currentSone.getOptions().getLoadLinkedImages().name()); + } + templateContext.set("insertion-delay", preferences.getInsertionDelay()); + templateContext.set("posts-per-page", preferences.getPostsPerPage()); + templateContext.set("images-per-page", preferences.getImagesPerPage()); + templateContext.set("characters-per-post", preferences.getCharactersPerPost()); + templateContext.set("post-cut-off-length", preferences.getPostCutOffLength()); + templateContext.set("require-full-access", preferences.isRequireFullAccess()); + templateContext.set("positive-trust", preferences.getPositiveTrust()); + templateContext.set("negative-trust", preferences.getNegativeTrust()); + templateContext.set("trust-comment", preferences.getTrustComment()); + templateContext.set("fcp-interface-active", preferences.isFcpInterfaceActive()); + templateContext.set("fcp-full-access-required", preferences.getFcpFullAccessRequired().ordinal()); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/ReloadingPage.java b/src/main/java/net/pterodactylus/sone/web/pages/ReloadingPage.java new file mode 100644 index 0000000..a36c3fd --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/ReloadingPage.java @@ -0,0 +1,83 @@ +/* + * Sone - ReloadingPage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.pterodactylus.util.io.StreamCopier; +import net.pterodactylus.util.web.Page; +import net.pterodactylus.util.web.Request; +import net.pterodactylus.util.web.Response; + +/** + * {@link Page} implementation that delivers static files from the filesystem. + * + * @param + * The type of the request + * @author David ‘Bombe’ Roden + */ +public class ReloadingPage implements Page { + + private final String pathPrefix; + private final String filesystemPath; + private final String mimeType; + + public ReloadingPage(String pathPrefix, String filesystemPathPrefix, String mimeType) { + this.pathPrefix = pathPrefix; + this.filesystemPath = filesystemPathPrefix; + this.mimeType = mimeType; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPath() { + return pathPrefix; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPrefixPage() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Response handleRequest(REQ request, Response response) throws IOException { + String path = request.getUri().getPath(); + int lastSlash = path.lastIndexOf('/'); + String filename = path.substring(lastSlash + 1); + try (InputStream fileInputStream = new FileInputStream(new File(filesystemPath, filename)); + OutputStream contentOutputStream = response.getContent()) { + StreamCopier.copy(fileInputStream, contentOutputStream); + } catch (FileNotFoundException fnfe1) { + return response.setStatusCode(404).setStatusText("Not found."); + } + return response.setStatusCode(200).setStatusText("OK").setContentType(mimeType); + } +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/RescuePage.java b/src/main/java/net/pterodactylus/sone/web/pages/RescuePage.java new file mode 100644 index 0000000..93ef9e1 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/RescuePage.java @@ -0,0 +1,74 @@ +/* + * Sone - RescuePage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static net.pterodactylus.sone.utils.NumberParsers.parseLong; + +import net.pterodactylus.sone.core.SoneRescuer; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user control the rescue mode for a Sone. + * + * @see SoneRescuer + * @author David ‘Bombe’ Roden + */ +public class RescuePage extends SoneTemplatePage { + + /** + * Creates a new rescue page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public RescuePage(Template template, WebInterface webInterface) { + super("rescue.html", template, "Page.Rescue.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + Sone currentSone = getCurrentSoneWithoutCreatingSession(request.getToadletContext()); + SoneRescuer soneRescuer = webInterface.getCore().getSoneRescuer(currentSone); + if (request.getMethod() == Method.POST) { + if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("fetch", 4))) { + long edition = parseLong(request.getHttpRequest().getPartAsStringFailsafe("edition", 8), -1L); + if (edition > -1) { + soneRescuer.setEdition(edition); + } + soneRescuer.startNextFetch(); + } + throw new RedirectException("rescue.html"); + } + templateContext.set("soneRescuer", soneRescuer); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/SearchPage.java b/src/main/java/net/pterodactylus/sone/web/pages/SearchPage.java new file mode 100644 index 0000000..ed6c507 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/SearchPage.java @@ -0,0 +1,659 @@ +/* + * Sone - SearchPage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static com.google.common.base.Optional.fromNullable; +import static com.google.common.primitives.Ints.tryParse; +import static java.util.logging.Logger.getLogger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; +import net.pterodactylus.sone.data.Reply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.collection.Pagination; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.text.StringEscaper; +import net.pterodactylus.util.text.TextException; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Collections2; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Ordering; + +/** + * This page lets the user search for posts and replies that contain certain + * words. + * + * @author David ‘Bombe’ Roden + */ +public class SearchPage extends SoneTemplatePage { + + /** The logger. */ + private static final Logger logger = getLogger(SearchPage.class.getName()); + + /** Short-term cache. */ + private final LoadingCache, Set>> hitCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader, Set>>() { + + @Override + @SuppressWarnings("synthetic-access") + public Set> load(List phrases) { + Set posts = new HashSet(); + for (Sone sone : webInterface.getCore().getSones()) { + posts.addAll(sone.getPosts()); + } + return getHits(Collections2.filter(posts, Post.FUTURE_POSTS_FILTER), phrases, new PostStringGenerator()); + } + }); + + /** + * Creates a new search page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public SearchPage(Template template, WebInterface webInterface) { + super("search.html", template, "Page.Search.Title", webInterface); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("synthetic-access") + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + String query = request.getHttpRequest().getParam("query").trim(); + if (query.length() == 0) { + throw new RedirectException("index.html"); + } + + List phrases = parseSearchPhrases(query); + if (phrases.isEmpty()) { + throw new RedirectException("index.html"); + } + + /* check for a couple of shortcuts. */ + if (phrases.size() == 1) { + String phrase = phrases.get(0).getPhrase(); + + /* is it a Sone ID? */ + redirectIfNotNull(getSoneId(phrase), "viewSone.html?sone="); + + /* is it a post ID? */ + redirectIfNotNull(getPostId(phrase), "viewPost.html?post="); + + /* is it a reply ID? show the post. */ + redirectIfNotNull(getReplyPostId(phrase), "viewPost.html?post="); + + /* is it an album ID? */ + redirectIfNotNull(getAlbumId(phrase), "imageBrowser.html?album="); + + /* is it an image ID? */ + redirectIfNotNull(getImageId(phrase), "imageBrowser.html?image="); + } + + Collection sones = webInterface.getCore().getSones(); + Collection> soneHits = getHits(sones, phrases, SoneStringGenerator.COMPLETE_GENERATOR); + + Collection> postHits = hitCache.getUnchecked(phrases); + + /* now filter. */ + soneHits = Collections2.filter(soneHits, Hit.POSITIVE_FILTER); + postHits = Collections2.filter(postHits, Hit.POSITIVE_FILTER); + + /* now sort. */ + List> sortedSoneHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(soneHits); + List> sortedPostHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(postHits); + + /* extract Sones and posts. */ + List resultSones = FluentIterable.from(sortedSoneHits).transform(new HitMapper()).toList(); + List resultPosts = FluentIterable.from(sortedPostHits).transform(new HitMapper()).toList(); + + /* pagination. */ + Pagination sonePagination = new Pagination(resultSones, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(fromNullable(tryParse(request.getHttpRequest().getParam("sonePage"))).or(0)); + Pagination postPagination = new Pagination(resultPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(fromNullable(tryParse(request.getHttpRequest().getParam("postPage"))).or(0)); + + templateContext.set("sonePagination", sonePagination); + templateContext.set("soneHits", sonePagination.getItems()); + templateContext.set("postPagination", postPagination); + templateContext.set("postHits", postPagination.getItems()); + } + + // + // PRIVATE METHODS + // + + /** + * Collects hit information for the given objects. The objects are converted + * to a {@link String} using the given {@link StringGenerator}, and the + * {@link #calculateScore(List, String) calculated score} is stored together + * with the object in a {@link Hit}, and all resulting {@link Hit}s are then + * returned. + * + * @param + * The type of the objects + * @param objects + * The objects to search over + * @param phrases + * The phrases to search for + * @param stringGenerator + * The string generator for the objects + * @return The hits for the given phrases + */ + private static Set> getHits(Collection objects, List phrases, StringGenerator stringGenerator) { + Set> hits = new HashSet>(); + for (T object : objects) { + String objectString = stringGenerator.generateString(object); + double score = calculateScore(phrases, objectString); + hits.add(new Hit(object, score)); + } + return hits; + } + + /** + * Parses the given query into search phrases. The query is split on + * whitespace while allowing to group words using single or double quotes. + * Isolated phrases starting with a “+” are + * {@link Phrase.Optionality#REQUIRED}, phrases with a “-” are + * {@link Phrase.Optionality#FORBIDDEN}. + * + * @param query + * The query to parse + * @return The parsed phrases + */ + private static List parseSearchPhrases(String query) { + List parsedPhrases; + try { + parsedPhrases = StringEscaper.parseLine(query); + } catch (TextException te1) { + /* invalid query. */ + return Collections.emptyList(); + } + + List phrases = new ArrayList(); + for (String phrase : parsedPhrases) { + if (phrase.startsWith("+")) { + if (phrase.length() > 1) { + phrases.add(new Phrase(phrase.substring(1), Phrase.Optionality.REQUIRED)); + } else { + phrases.add(new Phrase("+", Phrase.Optionality.OPTIONAL)); + } + } else if (phrase.startsWith("-")) { + if (phrase.length() > 1) { + phrases.add(new Phrase(phrase.substring(1), Phrase.Optionality.FORBIDDEN)); + } else { + phrases.add(new Phrase("-", Phrase.Optionality.OPTIONAL)); + } + } else { + phrases.add(new Phrase(phrase, Phrase.Optionality.OPTIONAL)); + } + } + return phrases; + } + + /** + * Calculates the score for the given expression when using the given + * phrases. + * + * @param phrases + * The phrases to search for + * @param expression + * The expression to search + * @return The score of the expression + */ + private static double calculateScore(List phrases, String expression) { + logger.log(Level.FINEST, String.format("Calculating Score for “%s”…", expression)); + double optionalHits = 0; + double requiredHits = 0; + int forbiddenHits = 0; + int requiredPhrases = 0; + for (Phrase phrase : phrases) { + String phraseString = phrase.getPhrase().toLowerCase(); + if (phrase.getOptionality() == Phrase.Optionality.REQUIRED) { + ++requiredPhrases; + } + int matches = 0; + int index = 0; + double score = 0; + while (index < expression.length()) { + int position = expression.toLowerCase().indexOf(phraseString, index); + if (position == -1) { + break; + } + score += Math.pow(1 - position / (double) expression.length(), 2); + index = position + phraseString.length(); + logger.log(Level.FINEST, String.format("Got hit at position %d.", position)); + ++matches; + } + logger.log(Level.FINEST, String.format("Score: %f", score)); + if (matches == 0) { + continue; + } + if (phrase.getOptionality() == Phrase.Optionality.REQUIRED) { + requiredHits += score; + } + if (phrase.getOptionality() == Phrase.Optionality.OPTIONAL) { + optionalHits += score; + } + if (phrase.getOptionality() == Phrase.Optionality.FORBIDDEN) { + forbiddenHits += matches; + } + } + return requiredHits * 3 + optionalHits + (requiredHits - requiredPhrases) * 5 - (forbiddenHits * 2); + } + + /** + * Throws a + * {@link net.pterodactylus.sone.web.page.FreenetTemplatePage.RedirectException} + * if the given object is not {@code null}, appending the object to the + * given target URL. + * + * @param object + * The object on which to redirect + * @param target + * The target of the redirect + * @throws RedirectException + * if {@code object} is not {@code null} + */ + private static void redirectIfNotNull(String object, String target) throws RedirectException { + if (object != null) { + throw new RedirectException(target + object); + } + } + + /** + * If the given phrase contains a Sone ID (optionally prefixed by + * “sone://”), returns said Sone ID, otherwise return {@code null}. + * + * @param phrase + * The phrase that maybe is a Sone ID + * @return The Sone ID, or {@code null} + */ + private String getSoneId(String phrase) { + String soneId = phrase.startsWith("sone://") ? phrase.substring(7) : phrase; + return (webInterface.getCore().getSone(soneId).isPresent()) ? soneId : null; + } + + /** + * If the given phrase contains a post ID (optionally prefixed by + * “post://”), returns said post ID, otherwise return {@code null}. + * + * @param phrase + * The phrase that maybe is a post ID + * @return The post ID, or {@code null} + */ + private String getPostId(String phrase) { + String postId = phrase.startsWith("post://") ? phrase.substring(7) : phrase; + return (webInterface.getCore().getPost(postId).isPresent()) ? postId : null; + } + + /** + * If the given phrase contains a reply ID (optionally prefixed by + * “reply://”), returns the ID of the post the reply belongs to, otherwise + * return {@code null}. + * + * @param phrase + * The phrase that maybe is a reply ID + * @return The reply’s post ID, or {@code null} + */ + private String getReplyPostId(String phrase) { + String replyId = phrase.startsWith("reply://") ? phrase.substring(8) : phrase; + Optional postReply = webInterface.getCore().getPostReply(replyId); + if (!postReply.isPresent()) { + return null; + } + return postReply.get().getPostId(); + } + + /** + * If the given phrase contains an album ID (optionally prefixed by + * “album://”), returns said album ID, otherwise return {@code null}. + * + * @param phrase + * The phrase that maybe is an album ID + * @return The album ID, or {@code null} + */ + private String getAlbumId(String phrase) { + String albumId = phrase.startsWith("album://") ? phrase.substring(8) : phrase; + return (webInterface.getCore().getAlbum(albumId) != null) ? albumId : null; + } + + /** + * If the given phrase contains an image ID (optionally prefixed by + * “image://”), returns said image ID, otherwise return {@code null}. + * + * @param phrase + * The phrase that maybe is an image ID + * @return The image ID, or {@code null} + */ + private String getImageId(String phrase) { + String imageId = phrase.startsWith("image://") ? phrase.substring(8) : phrase; + return (webInterface.getCore().getImage(imageId, false) != null) ? imageId : null; + } + + /** + * Converts a given object into a {@link String}. + * + * @param + * The type of the objects + * @author David ‘Bombe’ Roden + */ + private static interface StringGenerator { + + /** + * Generates a {@link String} for the given object. + * + * @param object + * The object to generate the {@link String} for + * @return The generated {@link String} + */ + public String generateString(T object); + + } + + /** + * Generates a {@link String} from a {@link Sone}, concatenating the name of + * the Sone and all {@link Profile} {@link Field} values. + * + * @author David ‘Bombe’ Roden + */ + private static class SoneStringGenerator implements StringGenerator { + + /** A static instance of a complete Sone string generator. */ + public static final SoneStringGenerator COMPLETE_GENERATOR = new SoneStringGenerator(true); + + /** + * A static instance of a Sone string generator that will only use the + * name of the Sone. + */ + public static final SoneStringGenerator NAME_GENERATOR = new SoneStringGenerator(false); + + /** Whether to generate a string from all data of a Sone. */ + private final boolean complete; + + /** + * Creates a new Sone string generator. + * + * @param complete + * {@code true} to use the profile’s fields, {@code false} to + * not to use the profile‘s fields + */ + private SoneStringGenerator(boolean complete) { + this.complete = complete; + } + + /** + * {@inheritDoc} + */ + @Override + public String generateString(Sone sone) { + StringBuilder soneString = new StringBuilder(); + soneString.append(sone.getName()); + Profile soneProfile = sone.getProfile(); + if (soneProfile.getFirstName() != null) { + soneString.append(' ').append(soneProfile.getFirstName()); + } + if (soneProfile.getMiddleName() != null) { + soneString.append(' ').append(soneProfile.getMiddleName()); + } + if (soneProfile.getLastName() != null) { + soneString.append(' ').append(soneProfile.getLastName()); + } + if (complete) { + for (Field field : soneProfile.getFields()) { + soneString.append(' ').append(field.getValue()); + } + } + return soneString.toString(); + } + + } + + /** + * Generates a {@link String} from a {@link Post}, concatenating the text of + * the post, the text of all {@link Reply}s, and the name of all + * {@link Sone}s that have replied. + * + * @author David ‘Bombe’ Roden + */ + private class PostStringGenerator implements StringGenerator { + + /** + * {@inheritDoc} + */ + @Override + public String generateString(Post post) { + StringBuilder postString = new StringBuilder(); + postString.append(post.getText()); + if (post.getRecipient().isPresent()) { + postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(post.getRecipient().get())); + } + for (PostReply reply : Collections2.filter(webInterface.getCore().getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER)) { + postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(reply.getSone())); + postString.append(' ').append(reply.getText()); + } + return postString.toString(); + } + + } + + /** + * A search phrase. + * + * @author David ‘Bombe’ Roden + */ + private static class Phrase { + + /** + * The optionality of a search phrase. + * + * @author David ‘Bombe’ + * Roden + */ + public enum Optionality { + + /** The phrase is optional. */ + OPTIONAL, + + /** The phrase is required. */ + REQUIRED, + + /** The phrase is forbidden. */ + FORBIDDEN + + } + + /** The phrase to search for. */ + private final String phrase; + + /** The optionality of the phrase. */ + private final Optionality optionality; + + /** + * Creates a new phrase. + * + * @param phrase + * The phrase to search for + * @param optionality + * The optionality of the phrase + */ + public Phrase(String phrase, Optionality optionality) { + this.optionality = optionality; + this.phrase = phrase; + } + + /** + * Returns the phrase to search for. + * + * @return The phrase to search for + */ + public String getPhrase() { + return phrase; + } + + /** + * Returns the optionality of the phrase. + * + * @return The optionality of the phrase + */ + public Optionality getOptionality() { + return optionality; + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return phrase.hashCode() ^ ((optionality == Optionality.FORBIDDEN) ? (0xaaaaaaaa) : ((optionality == Optionality.REQUIRED) ? 0x55555555 : 0)); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof Phrase)) { + return false; + } + Phrase phrase = (Phrase) object; + return (this.optionality == phrase.optionality) && this.phrase.equals(phrase.phrase); + } + + } + + /** + * A hit consists of a searched object and the score it got for the phrases + * of the search. + * + * @see SearchPage#calculateScore(List, String) + * @param + * The type of the searched object + * @author David ‘Bombe’ Roden + */ + private static class Hit { + + /** Filter for {@link Hit}s with a score of more than 0. */ + public static final Predicate> POSITIVE_FILTER = new Predicate>() { + + @Override + public boolean apply(Hit hit) { + return (hit != null) && (hit.getScore() > 0); + } + + }; + + /** Comparator that sorts {@link Hit}s descending by score. */ + public static final Comparator> DESCENDING_COMPARATOR = new Comparator>() { + + @Override + public int compare(Hit leftHit, Hit rightHit) { + return Double.compare(rightHit.getScore(), leftHit.getScore()); + } + + }; + + /** The object that was searched. */ + private final T object; + + /** The score of the object. */ + private final double score; + + /** + * Creates a new hit. + * + * @param object + * The object that was searched + * @param score + * The score of the object + */ + public Hit(T object, double score) { + this.object = object; + this.score = score; + } + + /** + * Returns the object that was searched. + * + * @return The object that was searched + */ + public T getObject() { + return object; + } + + /** + * Returns the score of the object. + * + * @return The score of the object + */ + public double getScore() { + return score; + } + + } + + /** + * Extracts the object from a {@link Hit}. + * + * @param + * The type of the object to extract + * @author David ‘Bombe’ Roden + */ + private static class HitMapper implements Function, T> { + + /** + * {@inheritDoc} + */ + @Override + public T apply(Hit input) { + return input.getObject(); + } + + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/SoneTemplatePage.java b/src/main/java/net/pterodactylus/sone/web/pages/SoneTemplatePage.java new file mode 100644 index 0000000..46c8318 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/SoneTemplatePage.java @@ -0,0 +1,281 @@ +/* + * Sone - SoneTemplatePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.main.SonePlugin; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.sone.web.page.FreenetTemplatePage; +import net.pterodactylus.util.notify.Notification; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +import freenet.clients.http.SessionManager.Session; +import freenet.clients.http.ToadletContext; +import freenet.support.api.HTTPRequest; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Base page for the Sone web interface. + * + * @author David ‘Bombe’ Roden + */ +public class SoneTemplatePage extends FreenetTemplatePage { + + /** The Sone core. */ + protected final WebInterface webInterface; + + /** The page title l10n key. */ + private final String pageTitleKey; + + /** Whether to require a login. */ + private final boolean requireLogin; + + /** + * Creates a new template page for Sone that does not require the user to be + * logged in. + * + * @param path + * The path of the page + * @param template + * The template to render + * @param pageTitleKey + * The l10n key of the page title + * @param webInterface + * The Sone web interface + */ + public SoneTemplatePage(String path, Template template, String pageTitleKey, WebInterface webInterface) { + this(path, template, pageTitleKey, webInterface, false); + } + + /** + * Creates a new template page for Sone. + * + * @param path + * The path of the page + * @param template + * The template to render + * @param webInterface + * The Sone web interface + * @param requireLogin + * Whether this page requires a login + */ + public SoneTemplatePage(String path, Template template, WebInterface webInterface, boolean requireLogin) { + this(path, template, null, webInterface, requireLogin); + } + + /** + * Creates a new template page for Sone. + * + * @param path + * The path of the page + * @param template + * The template to render + * @param pageTitleKey + * The l10n key of the page title + * @param webInterface + * The Sone web interface + * @param requireLogin + * Whether this page requires a login + */ + public SoneTemplatePage(String path, Template template, String pageTitleKey, WebInterface webInterface, boolean requireLogin) { + super(path, webInterface.getTemplateContextFactory(), template, "noPermission.html"); + this.pageTitleKey = pageTitleKey; + this.webInterface = webInterface; + this.requireLogin = requireLogin; + } + + // + // PROTECTED METHODS + // + + /** + * Returns the currently logged in Sone. + * + * @param toadletContext + * The toadlet context + * @return The currently logged in Sone, or {@code null} if no Sone is + * currently logged in + */ + protected Sone getCurrentSone(ToadletContext toadletContext) { + return webInterface.getCurrentSoneCreatingSession(toadletContext); + } + + protected Sone getCurrentSoneWithoutCreatingSession(ToadletContext toadletContext) { + return webInterface.getCurrentSoneWithoutCreatingSession(toadletContext); + } + + /** + * Sets the currently logged in Sone. + * + * @param toadletContext + * The toadlet context + * @param sone + * The Sone to set as currently logged in + */ + protected void setCurrentSone(ToadletContext toadletContext, Sone sone) { + webInterface.setCurrentSone(toadletContext, sone); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected String getPageTitle(FreenetRequest request) { + if (pageTitleKey != null) { + return webInterface.getL10n().getString(pageTitleKey); + } + return ""; + } + + /** + * {@inheritDoc} + */ + @Override + protected List> getAdditionalLinkNodes(FreenetRequest request) { + return ImmutableList.> builder().add(ImmutableMap. builder().put("rel", "search").put("type", "application/opensearchdescription+xml").put("title", "Sone").put("href", "http://" + request.getHttpRequest().getHeader("host") + "/Sone/OpenSearch.xml").build()).build(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Collection getStyleSheets() { + return Arrays.asList("css/sone.css"); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getShortcutIcon() { + return "images/icon.png"; + } + + /** + * Returns whether this page requires the user to log in. + * + * @return {@code true} if the user is required to be logged in to use this + * page, {@code false} otherwise + */ + protected boolean requiresLogin() { + return requireLogin; + } + + /** + * {@inheritDoc} + */ + @Override + protected final void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + super.processTemplate(request, templateContext); + Sone currentSone = getCurrentSoneWithoutCreatingSession(request.getToadletContext()); + templateContext.set("preferences", webInterface.getCore().getPreferences()); + templateContext.set("currentSone", currentSone); + templateContext.set("localSones", webInterface.getCore().getLocalSones()); + templateContext.set("request", request); + templateContext.set("currentVersion", SonePlugin.getPluginVersion()); + templateContext.set("hasLatestVersion", webInterface.getCore().getUpdateChecker().hasLatestVersion()); + templateContext.set("latestEdition", webInterface.getCore().getUpdateChecker().getLatestEdition()); + templateContext.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion()); + templateContext.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate()); + List notifications = new ArrayList(webInterface.getNotifications(currentSone)); + Collections.sort(notifications, Notification.CREATED_TIME_SORTER); + templateContext.set("notifications", notifications); + templateContext.set("notificationHash", notifications.hashCode()); + handleRequest(request, templateContext); + } + + protected void handleRequest(@Nonnull FreenetRequest request, @Nonnull TemplateContext templateContext) throws RedirectException { + } + + /** + * {@inheritDoc} + */ + @Override + protected String getRedirectTarget(FreenetRequest request) { + if (requiresLogin() && (getCurrentSoneWithoutCreatingSession(request.getToadletContext()) == null)) { + HTTPRequest httpRequest = request.getHttpRequest(); + String originalUrl = httpRequest.getPath(); + if (httpRequest.hasParameters()) { + StringBuilder requestParameters = new StringBuilder(); + for (String parameterName : httpRequest.getParameterNames()) { + if (requestParameters.length() > 0) { + requestParameters.append("&"); + } + String[] parameterValues = httpRequest.getMultipleParam(parameterName); + for (String parameterValue : parameterValues) { + requestParameters.append(urlEncode(parameterName)).append("=").append(urlEncode(parameterValue)); + } + } + originalUrl += "?" + requestParameters.toString(); + } + return "login.html?target=" + urlEncode(originalUrl); + } + return null; + } + + private static String urlEncode(String value) { + try { + return URLEncoder.encode(value, "UTF-8"); + } catch (UnsupportedEncodingException uee1) { + /* A JVM without UTF-8? I don’t think so. */ + throw new RuntimeException(uee1); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean isFullAccessOnly() { + return webInterface.getCore().getPreferences().isRequireFullAccess(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled(ToadletContext toadletContext) { + if (webInterface.getCore().getPreferences().isRequireFullAccess() && !toadletContext.isAllowedFullAccess()) { + return false; + } + if (requiresLogin()) { + return getCurrentSoneWithoutCreatingSession(toadletContext) != null; + } + return true; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/TrustPage.java b/src/main/java/net/pterodactylus/sone/web/pages/TrustPage.java new file mode 100644 index 0000000..55929d7 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/TrustPage.java @@ -0,0 +1,72 @@ +/* + * Sone - TrustPage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import com.google.common.base.Optional; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user trust another Sone. This will assign a configurable + * amount of trust to an identity. + * + * @see Core#trustSone(Sone, Sone) + * @author David ‘Bombe’ Roden + */ +public class TrustPage extends SoneTemplatePage { + + /** + * Creates a new “trust Sone” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public TrustPage(Template template, WebInterface webInterface) { + super("trust.html", template, "Page.Trust.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Optional sone = webInterface.getCore().getSone(identity); + if (sone.isPresent()) { + webInterface.getCore().trustSone(currentSone, sone.get()); + } + throw new RedirectException(returnPage); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/UnbookmarkPage.java b/src/main/java/net/pterodactylus/sone/web/pages/UnbookmarkPage.java new file mode 100644 index 0000000..6fa037f --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/UnbookmarkPage.java @@ -0,0 +1,78 @@ +/* + * Sone - UnbookmarkPage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import java.util.Set; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +import com.google.common.base.Optional; + +/** + * Page that lets the user unbookmark a post. + * + * @author David ‘Bombe’ Roden + */ +public class UnbookmarkPage extends SoneTemplatePage { + + /** + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public UnbookmarkPage(Template template, WebInterface webInterface) { + super("unbookmark.html", template, "Page.Unbookmark.Title", webInterface); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + String id = request.getHttpRequest().getPartAsStringFailsafe("post", 36); + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + Optional post = webInterface.getCore().getPost(id); + if (post.isPresent()) { + webInterface.getCore().unbookmarkPost(post.get()); + } + throw new RedirectException(returnPage); + } + String id = request.getHttpRequest().getParam("post"); + if (id.equals("allNotLoaded")) { + Set posts = webInterface.getCore().getBookmarkedPosts(); + for (Post post : posts) { + if (!post.isLoaded()) { + webInterface.getCore().unbookmarkPost(post); + } + } + throw new RedirectException("bookmarks.html"); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/UnfollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/pages/UnfollowSonePage.java new file mode 100644 index 0000000..ec377e2 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/UnfollowSonePage.java @@ -0,0 +1,64 @@ +/* + * Sone - UnfollowSonePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * This page lets the user unfollow another Sone. + * + * @author David ‘Bombe’ Roden + */ +public class UnfollowSonePage extends SoneTemplatePage { + + /** + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public UnfollowSonePage(Template template, WebInterface webInterface) { + super("unfollowSone.html", template, "Page.UnfollowSone.Title", webInterface, true); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + Sone currentSone = getCurrentSone(request.getToadletContext()); + String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 2000); + for (String soneId : soneIds.split("[ ,]+")) { + webInterface.getCore().unfollowSone(currentSone, soneId); + } + throw new RedirectException(returnPage); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/UnlikePage.java b/src/main/java/net/pterodactylus/sone/web/pages/UnlikePage.java new file mode 100644 index 0000000..9e61c4f --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/UnlikePage.java @@ -0,0 +1,70 @@ +/* + * Sone - UnlikePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user unlike a {@link Post}. + * + * @author David ‘Bombe’ Roden + */ +public class UnlikePage extends SoneTemplatePage { + + /** + * Creates a new “unlike post” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public UnlikePage(Template template, WebInterface webInterface) { + super("unlike.html", template, "Page.Unlike.Title", webInterface, true); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + String type = request.getHttpRequest().getPartAsStringFailsafe("type", 16); + String id = request.getHttpRequest().getPartAsStringFailsafe(type, 36); + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + Sone currentSone = getCurrentSone(request.getToadletContext()); + if ("post".equals(type)) { + currentSone.removeLikedPostId(id); + } else if ("reply".equals(type)) { + currentSone.removeLikedReplyId(id); + } + throw new RedirectException(returnPage); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/UnlockSonePage.java b/src/main/java/net/pterodactylus/sone/web/pages/UnlockSonePage.java new file mode 100644 index 0000000..960f486 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/UnlockSonePage.java @@ -0,0 +1,63 @@ +/* + * Sone - UnlockSonePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +/** + * This page lets the user unlock a {@link Sone} to allow its insertion. + * + * @author David ‘Bombe’ Roden + */ +public class UnlockSonePage extends SoneTemplatePage { + + /** + * Creates a new “unlock Sone” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public UnlockSonePage(Template template, WebInterface webInterface) { + super("unlockSone.html", template, "Page.UnlockSone.Title", webInterface); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + String soneId = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); + Sone sone = webInterface.getCore().getLocalSone(soneId); + if (sone != null) { + webInterface.getCore().unlockSone(sone); + } + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + throw new RedirectException(returnPage); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/UntrustPage.java b/src/main/java/net/pterodactylus/sone/web/pages/UntrustPage.java new file mode 100644 index 0000000..de0b086 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/UntrustPage.java @@ -0,0 +1,72 @@ +/* + * Sone - UntrustPage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import com.google.common.base.Optional; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +/** + * Page that lets the user untrust another Sone. This will remove all trust + * assignments for an identity. + * + * @see Core#untrustSone(Sone, Sone) + * @author David ‘Bombe’ Roden + */ +public class UntrustPage extends SoneTemplatePage { + + /** + * Creates a new “untrust Sone” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public UntrustPage(Template template, WebInterface webInterface) { + super("untrust.html", template, "Page.Untrust.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256); + String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44); + Sone currentSone = getCurrentSone(request.getToadletContext()); + Optional sone = webInterface.getCore().getSone(identity); + if (sone.isPresent()) { + webInterface.getCore().untrustSone(currentSone, sone.get()); + } + throw new RedirectException(returnPage); + } + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/UploadImagePage.java b/src/main/java/net/pterodactylus/sone/web/pages/UploadImagePage.java new file mode 100644 index 0000000..6b77132 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/UploadImagePage.java @@ -0,0 +1,170 @@ +/* + * Sone - UploadImagePage.java - Copyright © 2011–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static com.google.common.base.Optional.fromNullable; +import static java.util.logging.Logger.getLogger; + +import java.awt.Image; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; + +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Image.Modifier.ImageTitleMustNotBeEmpty; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.TemporaryImage; +import net.pterodactylus.sone.text.TextFilter; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.io.Closer; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; + +import com.google.common.io.ByteStreams; + +import freenet.support.api.Bucket; +import freenet.support.api.HTTPUploadedFile; + +/** + * Page implementation that lets the user upload an image. + * + * @author David ‘Bombe’ Roden + */ +public class UploadImagePage extends SoneTemplatePage { + + private static final Logger logger = getLogger(UploadImagePage.class.getName()); + private static final String UNKNOWN_MIME_TYPE = "application/octet-stream"; + + /** + * Creates a new “upload image” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public UploadImagePage(Template template, WebInterface webInterface) { + super("uploadImage.html", template, "Page.UploadImage.Title", webInterface, true); + } + + // + // SONETEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + if (request.getMethod() == Method.POST) { + Sone currentSone = getCurrentSone(request.getToadletContext()); + String parentId = request.getHttpRequest().getPartAsStringFailsafe("parent", 36); + Album parent = webInterface.getCore().getAlbum(parentId); + if (parent == null) { + throw new RedirectException("noPermission.html"); + } + if (!currentSone.equals(parent.getSone())) { + throw new RedirectException("noPermission.html"); + } + String name = request.getHttpRequest().getPartAsStringFailsafe("title", 200).trim(); + if (name.length() == 0) { + throw new RedirectException("emptyImageTitle.html"); + } + String description = request.getHttpRequest().getPartAsStringFailsafe("description", 4000); + HTTPUploadedFile uploadedFile = request.getHttpRequest().getUploadedFile("image"); + Bucket fileBucket = uploadedFile.getData(); + InputStream imageInputStream = null; + ByteArrayOutputStream imageDataOutputStream = null; + try { + imageInputStream = fileBucket.getInputStream(); + /* TODO - check length */ + imageDataOutputStream = new ByteArrayOutputStream((int) fileBucket.size()); + ByteStreams.copy(imageInputStream, imageDataOutputStream); + } catch (IOException ioe1) { + logger.log(Level.WARNING, "Could not read uploaded image!", ioe1); + return; + } finally { + fileBucket.free(); + Closer.close(imageInputStream); + Closer.close(imageDataOutputStream); + } + byte[] imageData = imageDataOutputStream.toByteArray(); + ByteArrayInputStream imageDataInputStream = null; + Image uploadedImage = null; + try { + imageDataInputStream = new ByteArrayInputStream(imageData); + uploadedImage = ImageIO.read(imageDataInputStream); + if (uploadedImage == null) { + templateContext.set("messages", webInterface.getL10n().getString("Page.UploadImage.Error.InvalidImage")); + return; + } + String mimeType = getMimeType(imageData); + TemporaryImage temporaryImage = webInterface.getCore().createTemporaryImage(mimeType, imageData); + net.pterodactylus.sone.data.Image image = webInterface.getCore().createImage(currentSone, parent, temporaryImage); + image.modify().setTitle(name).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description)).setWidth(uploadedImage.getWidth(null)).setHeight(uploadedImage.getHeight(null)).update(); + } catch (IOException ioe1) { + logger.log(Level.WARNING, "Could not read uploaded image!", ioe1); + return; + } catch (ImageTitleMustNotBeEmpty itmnbe) { + throw new RedirectException("emptyImageTitle.html"); + } finally { + Closer.close(imageDataInputStream); + Closer.flush(uploadedImage); + } + throw new RedirectException("imageBrowser.html?album=" + parent.getId()); + } + } + + // + // PRIVATE METHODS + // + + /** + * Tries to detect the MIME type of the encoded image. + * + * @param imageData + * The encoded image + * @return The MIME type of the image, or “application/octet-stream” if the + * image type could not be detected + */ + private static String getMimeType(byte[] imageData) { + ByteArrayInputStream imageDataInputStream = new ByteArrayInputStream(imageData); + try { + ImageInputStream imageInputStream = ImageIO.createImageInputStream(imageDataInputStream); + Iterator imageReaders = ImageIO.getImageReaders(imageInputStream); + if (imageReaders.hasNext()) { + return fromNullable(imageReaders.next().getOriginatingProvider().getMIMETypes()) + .or(new String[] { UNKNOWN_MIME_TYPE })[0]; + } + } catch (IOException ioe1) { + logger.log(Level.FINE, "Could not detect MIME type for image.", ioe1); + } + return UNKNOWN_MIME_TYPE; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/ViewPostPage.java b/src/main/java/net/pterodactylus/sone/web/pages/ViewPostPage.java new file mode 100644 index 0000000..12d6be9 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/ViewPostPage.java @@ -0,0 +1,90 @@ +/* + * Sone - ViewPostPage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import java.net.URI; + +import com.google.common.base.Optional; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.template.SoneAccessor; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +/** + * This page lets the user view a post and all its replies. + * + * @author David ‘Bombe’ Roden + */ +public class ViewPostPage extends SoneTemplatePage { + + /** + * Creates a new “view post” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public ViewPostPage(Template template, WebInterface webInterface) { + super("viewPost.html", template, "Page.ViewPost.Title", webInterface, false); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected String getPageTitle(FreenetRequest request) { + String postId = request.getHttpRequest().getParam("post"); + Optional post = webInterface.getCore().getPost(postId); + String title = ""; + if (post.isPresent()) { + title = post.get().getText().substring(0, Math.min(20, post.get().getText().length())) + "…"; + title += " - " + SoneAccessor.getNiceName(post.get().getSone()) + " - "; + } + title += webInterface.getL10n().getString("Page.ViewPost.Title"); + return title; + } + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + String postId = request.getHttpRequest().getParam("post"); + boolean raw = request.getHttpRequest().getParam("raw").equals("true"); + Optional post = webInterface.getCore().getPost(postId); + templateContext.set("post", post.orNull()); + templateContext.set("raw", raw); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return true; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/pages/ViewSonePage.java b/src/main/java/net/pterodactylus/sone/web/pages/ViewSonePage.java new file mode 100644 index 0000000..257d2b2 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/pages/ViewSonePage.java @@ -0,0 +1,130 @@ +/* + * Sone - ViewSonePage.java - Copyright © 2010–2016 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.pages; + +import static net.pterodactylus.sone.utils.NumberParsers.parseInt; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.template.SoneAccessor; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.util.collection.Pagination; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; + +import com.google.common.base.Optional; + +/** + * Lets the user browser another Sone. + * + * @author David ‘Bombe’ Roden + */ +public class ViewSonePage extends SoneTemplatePage { + + /** + * Creates a new “view Sone” page. + * + * @param template + * The template to render + * @param webInterface + * The Sone web interface + */ + public ViewSonePage(Template template, WebInterface webInterface) { + super("viewSone.html", template, webInterface, false); + } + + // + // TEMPLATEPAGE METHODS + // + + /** + * {@inheritDoc} + */ + @Override + protected String getPageTitle(FreenetRequest request) { + String soneId = request.getHttpRequest().getParam("sone"); + Optional sone = webInterface.getCore().getSone(soneId); + if (sone.isPresent()) { + String soneName = SoneAccessor.getNiceName(sone.get()); + return soneName + " - " + webInterface.getL10n().getString("Page.ViewSone.Title"); + } + return webInterface.getL10n().getString("Page.ViewSone.Page.TitleWithoutSone"); + } + + /** + * {@inheritDoc} + */ + @Override + protected void handleRequest(FreenetRequest request, TemplateContext templateContext) throws RedirectException { + String soneId = request.getHttpRequest().getParam("sone"); + Optional sone = webInterface.getCore().getSone(soneId); + templateContext.set("sone", sone.orNull()); + templateContext.set("soneId", soneId); + if (!sone.isPresent()) { + return; + } + List sonePosts = sone.get().getPosts(); + sonePosts.addAll(webInterface.getCore().getDirectedPosts(sone.get().getId())); + Collections.sort(sonePosts, Post.NEWEST_FIRST); + Pagination postPagination = new Pagination(sonePosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("postPage"), 0)); + templateContext.set("postPagination", postPagination); + templateContext.set("posts", postPagination.getItems()); + Set replies = sone.get().getReplies(); + final Map> repliedPosts = new HashMap>(); + for (PostReply reply : replies) { + Optional post = reply.getPost(); + if (!post.isPresent() || repliedPosts.containsKey(post.get()) || sone.get().equals(post.get().getSone()) || (sone.get().getId().equals(post.get().getRecipientId().orNull()))) { + continue; + } + repliedPosts.put(post.get(), webInterface.getCore().getReplies(post.get().getId())); + } + List posts = new ArrayList(repliedPosts.keySet()); + Collections.sort(posts, new Comparator() { + + @Override + public int compare(Post leftPost, Post rightPost) { + return (int) Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, repliedPosts.get(rightPost).get(0).getTime() - repliedPosts.get(leftPost).get(0).getTime())); + } + + }); + + Pagination repliedPostPagination = new Pagination(posts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("repliedPostPage"), 0)); + templateContext.set("repliedPostPagination", repliedPostPagination); + templateContext.set("repliedPosts", repliedPostPagination.getItems()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLinkExcepted(URI link) { + return true; + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/AboutPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/AboutPage.kt deleted file mode 100644 index da60e98..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/AboutPage.kt +++ /dev/null @@ -1,24 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.main.SonePlugin.PluginHomepage -import net.pterodactylus.sone.main.SonePlugin.PluginVersion -import net.pterodactylus.sone.main.SonePlugin.PluginYear -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * A [SoneTemplatePage] that stores information about Sone in the [TemplateContext]. - */ -class AboutPage(template: Template, webInterface: WebInterface, - private val pluginVersion: PluginVersion, - private val pluginYear: PluginYear, - private val pluginHomepage: PluginHomepage): SoneTemplatePage("about.html", template, "Page.About.Title", webInterface, false) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - templateContext["version"] = pluginVersion.version - templateContext["year"] = pluginYear.year - templateContext["homepage"] = pluginHomepage.homepage - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/BookmarkPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/BookmarkPage.kt deleted file mode 100644 index 5bc21d1..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/BookmarkPage.kt +++ /dev/null @@ -1,25 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user bookmark a post. - */ -class BookmarkPage(template: Template, webInterface: WebInterface) - : SoneTemplatePage("bookmark.html", template, "Page.Bookmark.Title", webInterface) { - - override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) { - if (freenetRequest.isPOST) { - val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256) - val postId = freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36) - webInterface.core.getPost(postId).orNull()?.let { - webInterface.core.bookmarkPost(it) - } - throw RedirectException(returnPage) - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/BookmarksPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/BookmarksPage.kt deleted file mode 100644 index 25d3317..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/BookmarksPage.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.collection.Pagination -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user browse all his bookmarked posts. - */ -class BookmarksPage(template: Template, webInterface: WebInterface): SoneTemplatePage("bookmarks.html", template, "Page.Bookmarks.Title", webInterface) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - webInterface.core.bookmarkedPosts.let { posts -> - val pagination = Pagination(posts.filter { it.isLoaded }.sortedByDescending { it.time }, webInterface.core.preferences.postsPerPage) - templateContext["pagination"] = pagination - templateContext["posts"] = pagination.items - templateContext["postsNotLoaded"] = posts.any { !it.isLoaded } - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/CreatePostPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/CreatePostPage.kt deleted file mode 100644 index 302fdf5..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/CreatePostPage.kt +++ /dev/null @@ -1,31 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.text.TextFilter -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * This page lets the user create a new [Post]. - */ -class CreatePostPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("createPost.html", template, "Page.CreatePost.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256) - templateContext["returnPage"] = returnPage - if (request.isPOST) { - val text = request.httpRequest.getPartAsStringFailsafe("text", 65536).trim() - if (text == "") { - templateContext["errorTextEmpty"] = true - return - } - val sender = webInterface.core.getLocalSone(request.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: getCurrentSone(request.toadletContext) - val recipient = webInterface.core.getSone(request.httpRequest.getPartAsStringFailsafe("recipient", 43)) - webInterface.core.createPost(sender, recipient, TextFilter.filter(request.httpRequest.getHeader("Host"), text)) - throw RedirectException(returnPage) - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/CreateSonePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/CreateSonePage.kt deleted file mode 100644 index 673cca3..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/CreateSonePage.kt +++ /dev/null @@ -1,44 +0,0 @@ -package net.pterodactylus.sone.web - -import freenet.clients.http.ToadletContext -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext -import java.util.logging.Level -import java.util.logging.Logger - -/** - * The “create Sone” page lets the user create a new Sone. - */ -class CreateSonePage(template: Template, webInterface: WebInterface): - SoneTemplatePage("createSone.html", template, "Page.CreateSone.Title", webInterface, false) { - - private val logger = Logger.getLogger(CreateSonePage::class.java.name) - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - templateContext["sones"] = webInterface.core.localSones.sortedWith(Sone.NICE_NAME_COMPARATOR) - templateContext["identitiesWithoutSone"] = webInterface.core.identityManager.allOwnIdentities.filterNot { "Sone" in it.contexts }.sortedBy { "${it.nickname}@${it.id}".toLowerCase() } - if (request.isPOST) { - val identity = request.httpRequest.getPartAsStringFailsafe("identity", 43) - webInterface.core.identityManager.allOwnIdentities.firstOrNull { it.id == identity }?.let { ownIdentity -> - val sone = webInterface.core.createSone(ownIdentity) - if (sone == null) { - logger.log(Level.SEVERE, "Could not create Sone for OwnIdentity: $ownIdentity") - } - setCurrentSone(request.toadletContext, sone) - throw RedirectException("index.html") - } - templateContext["errorNoIdentity"] = true - } - } - - override fun isEnabled(toadletContext: ToadletContext) = - if (webInterface.core.preferences.isRequireFullAccess && !toadletContext.isAllowedFullAccess) { - false - } else { - (getCurrentSone(toadletContext) == null) || (webInterface.core.localSones.size == 1) - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/DeleteAlbumPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/DeleteAlbumPage.kt deleted file mode 100644 index 7830653..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/DeleteAlbumPage.kt +++ /dev/null @@ -1,29 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user delete an {@link Album}. - */ -class DeleteAlbumPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("deleteAlbum.html", template, "Page.DeleteAlbum.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - val album = webInterface.core.getAlbum(request.httpRequest.getPartAsStringFailsafe("album", 36)) - templateContext["album"] = album ?: throw RedirectException("invalid.html") - if (request.isPOST) { - if (!album.sone.isLocal) { - throw RedirectException("noPermission.html") - } - if (request.httpRequest.getPartAsStringFailsafe("abortDelete", 4) == "true") { - throw RedirectException("imageBrowser.html?album=${album.id}") - } - webInterface.core.deleteAlbum(album) - throw RedirectException(if (album.parent.isRoot) "imageBrowser.html?sone=${album.sone.id}" else "imageBrowser.html?album=${album.parent.id}") - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/DeleteImagePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/DeleteImagePage.kt deleted file mode 100644 index 00ebc55..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/DeleteImagePage.kt +++ /dev/null @@ -1,29 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user delete an {@link Image}. - */ -class DeleteImagePage(template: Template, webInterface: WebInterface): - SoneTemplatePage("deleteImage.html", template, "Page.DeleteImage.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - val image = webInterface.core.getImage(request.httpRequest.getPartAsStringFailsafe("image", 36)) ?: throw RedirectException("invalid.html") - if (!image.sone.isLocal) { - throw RedirectException("noPermission.html") - } - if (request.isPOST) { - if (request.httpRequest.isPartSet("abortDelete")) { - throw RedirectException("imageBrowser.html?image=${image.id}") - } - webInterface.core.deleteImage(image) - throw RedirectException("imageBrowser.html?album=${image.album.id}") - } - templateContext["image"] = image - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/DeletePostPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/DeletePostPage.kt deleted file mode 100644 index bbbb897..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/DeletePostPage.kt +++ /dev/null @@ -1,32 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Lets the user delete a post they made. - */ -class DeletePostPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("deletePost.html", template, "Page.DeletePost.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - val post = webInterface.core.getPost(request.httpRequest.getPartAsStringFailsafe("post", 36)).orNull() ?: throw RedirectException("noPermission.html") - val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256) - if (request.isPOST) { - if (!post.sone.isLocal) { - throw RedirectException("noPermission.html") - } - if (request.httpRequest.isPartSet("confirmDelete")) { - webInterface.core.deletePost(post) - throw RedirectException(returnPage) - } else if (request.httpRequest.isPartSet("abortDelete")) { - throw RedirectException(returnPage) - } - } - templateContext["post"] = post - templateContext["returnPage"] = returnPage - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/DeleteProfileFieldPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/DeleteProfileFieldPage.kt deleted file mode 100644 index c957bd7..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/DeleteProfileFieldPage.kt +++ /dev/null @@ -1,26 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user confirm the deletion of a profile field. - */ -class DeleteProfileFieldPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("deleteProfileField.html", template, "Page.DeleteProfileField.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - val currentSone = getCurrentSone(request.toadletContext) - val field = currentSone.profile.getFieldById(request.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html") - templateContext["field"] = field - if (request.isPOST) { - if (request.httpRequest.getPartAsStringFailsafe("confirm", 4) == "true") { - currentSone.profile = currentSone.profile.apply { removeField(field) } - } - throw RedirectException("editProfile.html#profile-fields") - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/DeleteReplyPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/DeleteReplyPage.kt deleted file mode 100644 index 5a707ce..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/DeleteReplyPage.kt +++ /dev/null @@ -1,34 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * This page lets the user delete a reply. - */ -class DeleteReplyPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("deleteReply.html", template, "Page.DeleteReply.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - val replyId = request.httpRequest.getPartAsStringFailsafe("reply", 36) - templateContext["reply"] = replyId - val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256) - templateContext["returnPage"] = returnPage - if (request.isPOST) { - val reply = webInterface.core.getPostReply(replyId).orNull() ?: throw RedirectException("noPermission.html") - if (!reply.sone.isLocal) { - throw RedirectException("noPermission.html") - } - if (request.httpRequest.isPartSet("confirmDelete")) { - webInterface.core.deleteReply(reply) - throw RedirectException(returnPage) - } - if (request.httpRequest.isPartSet("abortDelete")) { - throw RedirectException(returnPage) - } - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/DeleteSonePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/DeleteSonePage.kt deleted file mode 100644 index 1d0f6aa..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/DeleteSonePage.kt +++ /dev/null @@ -1,25 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Lets the user delete a Sone. Of course the Sone is not really deleted from - * Freenet; merely all references to it are removed from the local plugin - * installation. - */ -class DeleteSonePage(template: Template, webInterface: WebInterface): - SoneTemplatePage("deleteSone.html", template, "Page.DeleteSone.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - if (request.isPOST) { - if (request.httpRequest.isPartSet("deleteSone")) { - webInterface.core.deleteSone(getCurrentSone(request.toadletContext)) - } - throw RedirectException("index.html") - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/DismissNotificationPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/DismissNotificationPage.kt deleted file mode 100644 index 27bb744..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/DismissNotificationPage.kt +++ /dev/null @@ -1,20 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user dismiss a notification. - */ -class DismissNotificationPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("dismissNotification.html", template, "Page.DismissNotification.Title", webInterface) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256) - val notificationId = request.httpRequest.getPartAsStringFailsafe("notification", 36) - webInterface.getNotification(notificationId).orNull()?.takeIf { it.isDismissable }?.dismiss() - throw RedirectException(returnPage) - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/DistrustPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/DistrustPage.kt deleted file mode 100644 index f3e593e..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/DistrustPage.kt +++ /dev/null @@ -1,25 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user distrust another Sone. This will assign a - * configurable (negative) amount of trust to an identity. - * - * @see net.pterodactylus.sone.core.Core#distrustSone(Sone, Sone) - */ -class DistrustPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("distrust.html", template, "Page.Distrust.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - if (request.isPOST) { - val sone = webInterface.core.getSone(request.httpRequest.getPartAsStringFailsafe("sone", 44)).orNull() - sone?.run { webInterface.core.distrustSone(getCurrentSone(request.toadletContext), this) } - throw RedirectException(request.httpRequest.getPartAsStringFailsafe("returnPage", 256)) - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/EditAlbumPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/EditAlbumPage.kt deleted file mode 100644 index 85ee17a..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/EditAlbumPage.kt +++ /dev/null @@ -1,42 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * Page that lets the user edit the name and description of an album. - */ -class EditAlbumPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("editAlbum.html", template, "Page.EditAlbum.Title", webInterface, true) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - if (request.isPOST) { - val album = webInterface.core.getAlbum(request.httpRequest.getPartAsStringFailsafe("album", 36)) ?: throw RedirectException("invalid.html") - album.takeUnless { it.sone.isLocal }?.run { throw RedirectException("noPermission.html") } - if (request.httpRequest.getPartAsStringFailsafe("moveLeft", 4) == "true") { - album.parent?.moveAlbumUp(album) - webInterface.core.touchConfiguration() - throw RedirectException("imageBrowser.html?album=${album.parent?.id}") - } else if (request.httpRequest.getPartAsStringFailsafe("moveRight", 4) == "true") { - album.parent?.moveAlbumDown(album) - webInterface.core.touchConfiguration() - throw RedirectException("imageBrowser.html?album=${album.parent?.id}") - } else { - try { - album.modify() - .setTitle(request.httpRequest.getPartAsStringFailsafe("title", 100)) - .setDescription(request.httpRequest.getPartAsStringFailsafe("description", 1000)) - .update() - } catch (e: AlbumTitleMustNotBeEmpty) { - throw RedirectException("emptyAlbumTitle.html") - } - webInterface.core.touchConfiguration() - throw RedirectException("imageBrowser.html?album=${album.id}") - } - } - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/LoginPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/LoginPage.kt deleted file mode 100644 index 302e5e4..0000000 --- a/src/main/kotlin/net/pterodactylus/sone/web/LoginPage.kt +++ /dev/null @@ -1,37 +0,0 @@ -package net.pterodactylus.sone.web - -import freenet.clients.http.ToadletContext -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.utils.isPOST -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.template.Template -import net.pterodactylus.util.template.TemplateContext - -/** - * The login page lets the user log in. - */ -class LoginPage(template: Template, webInterface: WebInterface): - SoneTemplatePage("login.html", template, "Page.Login.Title", webInterface) { - - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - if (request.isPOST) { - val soneId = request.httpRequest.getPartAsStringFailsafe("sone-id", 43) - webInterface.core.getLocalSone(soneId)?.let { sone -> - setCurrentSone(request.toadletContext, sone) - val target = if (request.httpRequest.isParameterSet("target")) request.httpRequest.getPartAsStringFailsafe("target", 256) else "index.html" - throw RedirectException(target) - } - } - templateContext["sones"] = webInterface.core.localSones.sortedWith(Sone.NICE_NAME_COMPARATOR) - templateContext["identitiesWithoutSone"] = webInterface.core.identityManager.allOwnIdentities.filterNot { "Sone" in it.contexts }.sortedBy { "${it.nickname}@${it.id}" } - } - - override public fun getRedirectTarget(request: FreenetRequest) = - getCurrentSone(request.toadletContext)?.let { "index.html" } - - override fun isEnabled(toadletContext: ToadletContext) = when { - webInterface.core.preferences.isRequireFullAccess && !toadletContext.isAllowedFullAccess -> false - else -> getCurrentSoneWithoutCreatingSession(toadletContext) == null - } - -} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/AboutPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/AboutPage.kt new file mode 100644 index 0000000..7809817 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/AboutPage.kt @@ -0,0 +1,26 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.main.SonePlugin.PluginHomepage +import net.pterodactylus.sone.main.SonePlugin.PluginVersion +import net.pterodactylus.sone.main.SonePlugin.PluginYear +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * A [SoneTemplatePage] that stores information about Sone in the [TemplateContext]. + */ +class AboutPage(template: Template, webInterface: WebInterface, + private val pluginVersion: PluginVersion, + private val pluginYear: PluginYear, + private val pluginHomepage: PluginHomepage): SoneTemplatePage("about.html", template, "Page.About.Title", webInterface, false) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + templateContext["version"] = pluginVersion.version + templateContext["year"] = pluginYear.year + templateContext["homepage"] = pluginHomepage.homepage + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarkPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarkPage.kt new file mode 100644 index 0000000..f1e8ac2 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarkPage.kt @@ -0,0 +1,27 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user bookmark a post. + */ +class BookmarkPage(template: Template, webInterface: WebInterface) + : SoneTemplatePage("bookmark.html", template, "Page.Bookmark.Title", webInterface) { + + override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) { + if (freenetRequest.isPOST) { + val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256) + val postId = freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36) + webInterface.core.getPost(postId).orNull()?.let { + webInterface.core.bookmarkPost(it) + } + throw RedirectException(returnPage) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarksPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarksPage.kt new file mode 100644 index 0000000..757ae75 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarksPage.kt @@ -0,0 +1,25 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.collection.Pagination +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user browse all his bookmarked posts. + */ +class BookmarksPage(template: Template, webInterface: WebInterface): SoneTemplatePage("bookmarks.html", template, "Page.Bookmarks.Title", webInterface) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + webInterface.core.bookmarkedPosts.let { posts -> + val pagination = Pagination(posts.filter { it.isLoaded }.sortedByDescending { it.time }, webInterface.core.preferences.postsPerPage) + templateContext["pagination"] = pagination + templateContext["posts"] = pagination.items + templateContext["postsNotLoaded"] = posts.any { !it.isLoaded } + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPage.kt new file mode 100644 index 0000000..3a628dd --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPage.kt @@ -0,0 +1,43 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty +import net.pterodactylus.sone.text.TextFilter +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user create a new album. + */ +class CreateAlbumPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("createAlbum.html", template, "Page.CreateAlbum.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + if (request.isPOST) { + val name = request.httpRequest.getPartAsStringFailsafe("name", 64).trim() + if (name.isEmpty()) { + templateContext["nameMissing"] = true + return + } + val description = request.httpRequest.getPartAsStringFailsafe("description", 256).trim() + val currentSone = webInterface.getCurrentSoneCreatingSession(request.toadletContext) + val parentId = request.httpRequest.getPartAsStringFailsafe("parent", 36) + val parent = if (parentId == "") currentSone.rootAlbum else webInterface.core.getAlbum(parentId) + val album = webInterface.core.createAlbum(currentSone, parent) + try { + album.modify().apply { + setTitle(name) + setDescription(TextFilter.filter(request.httpRequest.getHeader("Host"), description)) + }.update() + } catch (e: AlbumTitleMustNotBeEmpty) { + throw RedirectException("emptyAlbumTitle.html") + } + webInterface.core.touchConfiguration() + throw RedirectException("imageBrowser.html?album=${album.id}") + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/CreatePostPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/CreatePostPage.kt new file mode 100644 index 0000000..6e39d49 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/CreatePostPage.kt @@ -0,0 +1,33 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.text.TextFilter +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * This page lets the user create a new [Post]. + */ +class CreatePostPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("createPost.html", template, "Page.CreatePost.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256) + templateContext["returnPage"] = returnPage + if (request.isPOST) { + val text = request.httpRequest.getPartAsStringFailsafe("text", 65536).trim() + if (text == "") { + templateContext["errorTextEmpty"] = true + return + } + val sender = webInterface.core.getLocalSone(request.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: getCurrentSone(request.toadletContext) + val recipient = webInterface.core.getSone(request.httpRequest.getPartAsStringFailsafe("recipient", 43)) + webInterface.core.createPost(sender, recipient, TextFilter.filter(request.httpRequest.getHeader("Host"), text)) + throw RedirectException(returnPage) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPage.kt new file mode 100644 index 0000000..2283fee --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPage.kt @@ -0,0 +1,33 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.text.TextFilter +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * This page lets the user post a reply to a post. + */ +class CreateReplyPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("createReply.html", template, "Page.CreateReply.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + val postId = request.httpRequest.getPartAsStringFailsafe("post", 36).apply { templateContext["postId"] = this } + val text = request.httpRequest.getPartAsStringFailsafe("text", 65536).trim().apply { templateContext["text"] = this } + val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256).apply { templateContext["returnPage"] = this } + if (request.isPOST) { + if (text == "") { + templateContext["errorTextEmpty"] = true + return + } + val post = webInterface.core.getPost(postId).orNull() ?: throw RedirectException("noPermission.html") + val sender = webInterface.core.getLocalSone(request.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: getCurrentSone(request.toadletContext) + webInterface.core.createReply(sender, post, TextFilter.filter(request.httpRequest.getHeader("Host"), text)) + throw RedirectException(returnPage) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateSonePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateSonePage.kt new file mode 100644 index 0000000..f864b73 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/CreateSonePage.kt @@ -0,0 +1,46 @@ +package net.pterodactylus.sone.web.pages + +import freenet.clients.http.ToadletContext +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext +import java.util.logging.Level +import java.util.logging.Logger + +/** + * The “create Sone” page lets the user create a new Sone. + */ +class CreateSonePage(template: Template, webInterface: WebInterface): + SoneTemplatePage("createSone.html", template, "Page.CreateSone.Title", webInterface, false) { + + private val logger = Logger.getLogger(CreateSonePage::class.java.name) + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + templateContext["sones"] = webInterface.core.localSones.sortedWith(Sone.NICE_NAME_COMPARATOR) + templateContext["identitiesWithoutSone"] = webInterface.core.identityManager.allOwnIdentities.filterNot { "Sone" in it.contexts }.sortedBy { "${it.nickname}@${it.id}".toLowerCase() } + if (request.isPOST) { + val identity = request.httpRequest.getPartAsStringFailsafe("identity", 43) + webInterface.core.identityManager.allOwnIdentities.firstOrNull { it.id == identity }?.let { ownIdentity -> + val sone = webInterface.core.createSone(ownIdentity) + if (sone == null) { + logger.log(Level.SEVERE, "Could not create Sone for OwnIdentity: $ownIdentity") + } + setCurrentSone(request.toadletContext, sone) + throw RedirectException("index.html") + } + templateContext["errorNoIdentity"] = true + } + } + + override fun isEnabled(toadletContext: ToadletContext) = + if (webInterface.core.preferences.isRequireFullAccess && !toadletContext.isAllowedFullAccess) { + false + } else { + (getCurrentSone(toadletContext) == null) || (webInterface.core.localSones.size == 1) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPage.kt new file mode 100644 index 0000000..9e6bf38 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPage.kt @@ -0,0 +1,31 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user delete an {@link Album}. + */ +class DeleteAlbumPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("deleteAlbum.html", template, "Page.DeleteAlbum.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + val album = webInterface.core.getAlbum(request.httpRequest.getPartAsStringFailsafe("album", 36)) + templateContext["album"] = album ?: throw RedirectException("invalid.html") + if (request.isPOST) { + if (!album.sone.isLocal) { + throw RedirectException("noPermission.html") + } + if (request.httpRequest.getPartAsStringFailsafe("abortDelete", 4) == "true") { + throw RedirectException("imageBrowser.html?album=${album.id}") + } + webInterface.core.deleteAlbum(album) + throw RedirectException(if (album.parent.isRoot) "imageBrowser.html?sone=${album.sone.id}" else "imageBrowser.html?album=${album.parent.id}") + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePage.kt new file mode 100644 index 0000000..183705a --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePage.kt @@ -0,0 +1,31 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user delete an {@link Image}. + */ +class DeleteImagePage(template: Template, webInterface: WebInterface): + SoneTemplatePage("deleteImage.html", template, "Page.DeleteImage.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + val image = webInterface.core.getImage(request.httpRequest.getPartAsStringFailsafe("image", 36)) ?: throw RedirectException("invalid.html") + if (!image.sone.isLocal) { + throw RedirectException("noPermission.html") + } + if (request.isPOST) { + if (request.httpRequest.isPartSet("abortDelete")) { + throw RedirectException("imageBrowser.html?image=${image.id}") + } + webInterface.core.deleteImage(image) + throw RedirectException("imageBrowser.html?album=${image.album.id}") + } + templateContext["image"] = image + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/DeletePostPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeletePostPage.kt new file mode 100644 index 0000000..99ccb3a --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeletePostPage.kt @@ -0,0 +1,34 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Lets the user delete a post they made. + */ +class DeletePostPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("deletePost.html", template, "Page.DeletePost.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + val post = webInterface.core.getPost(request.httpRequest.getPartAsStringFailsafe("post", 36)).orNull() ?: throw RedirectException("noPermission.html") + val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256) + if (request.isPOST) { + if (!post.sone.isLocal) { + throw RedirectException("noPermission.html") + } + if (request.httpRequest.isPartSet("confirmDelete")) { + webInterface.core.deletePost(post) + throw RedirectException(returnPage) + } else if (request.httpRequest.isPartSet("abortDelete")) { + throw RedirectException(returnPage) + } + } + templateContext["post"] = post + templateContext["returnPage"] = returnPage + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPage.kt new file mode 100644 index 0000000..e0dc4be --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPage.kt @@ -0,0 +1,28 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user confirm the deletion of a profile field. + */ +class DeleteProfileFieldPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("deleteProfileField.html", template, "Page.DeleteProfileField.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + val currentSone = getCurrentSone(request.toadletContext) + val field = currentSone.profile.getFieldById(request.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html") + templateContext["field"] = field + if (request.isPOST) { + if (request.httpRequest.getPartAsStringFailsafe("confirm", 4) == "true") { + currentSone.profile = currentSone.profile.apply { removeField(field) } + } + throw RedirectException("editProfile.html#profile-fields") + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPage.kt new file mode 100644 index 0000000..f9c51a3 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPage.kt @@ -0,0 +1,36 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * This page lets the user delete a reply. + */ +class DeleteReplyPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("deleteReply.html", template, "Page.DeleteReply.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + val replyId = request.httpRequest.getPartAsStringFailsafe("reply", 36) + templateContext["reply"] = replyId + val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256) + templateContext["returnPage"] = returnPage + if (request.isPOST) { + val reply = webInterface.core.getPostReply(replyId).orNull() ?: throw RedirectException("noPermission.html") + if (!reply.sone.isLocal) { + throw RedirectException("noPermission.html") + } + if (request.httpRequest.isPartSet("confirmDelete")) { + webInterface.core.deleteReply(reply) + throw RedirectException(returnPage) + } + if (request.httpRequest.isPartSet("abortDelete")) { + throw RedirectException(returnPage) + } + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePage.kt new file mode 100644 index 0000000..299fb80 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePage.kt @@ -0,0 +1,27 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Lets the user delete a Sone. Of course the Sone is not really deleted from + * Freenet; merely all references to it are removed from the local plugin + * installation. + */ +class DeleteSonePage(template: Template, webInterface: WebInterface): + SoneTemplatePage("deleteSone.html", template, "Page.DeleteSone.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + if (request.isPOST) { + if (request.httpRequest.isPartSet("deleteSone")) { + webInterface.core.deleteSone(getCurrentSone(request.toadletContext)) + } + throw RedirectException("index.html") + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPage.kt new file mode 100644 index 0000000..6e8a3d2 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPage.kt @@ -0,0 +1,22 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user dismiss a notification. + */ +class DismissNotificationPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("dismissNotification.html", template, "Page.DismissNotification.Title", webInterface) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + val returnPage = request.httpRequest.getPartAsStringFailsafe("returnPage", 256) + val notificationId = request.httpRequest.getPartAsStringFailsafe("notification", 36) + webInterface.getNotification(notificationId).orNull()?.takeIf { it.isDismissable }?.dismiss() + throw RedirectException(returnPage) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/DistrustPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/DistrustPage.kt new file mode 100644 index 0000000..bb70ecc --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/DistrustPage.kt @@ -0,0 +1,27 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user distrust another Sone. This will assign a + * configurable (negative) amount of trust to an identity. + * + * @see net.pterodactylus.sone.core.Core#distrustSone(Sone, Sone) + */ +class DistrustPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("distrust.html", template, "Page.Distrust.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + if (request.isPOST) { + val sone = webInterface.core.getSone(request.httpRequest.getPartAsStringFailsafe("sone", 44)).orNull() + sone?.run { webInterface.core.distrustSone(getCurrentSone(request.toadletContext), this) } + throw RedirectException(request.httpRequest.getPartAsStringFailsafe("returnPage", 256)) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPage.kt new file mode 100644 index 0000000..1e45744 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPage.kt @@ -0,0 +1,44 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * Page that lets the user edit the name and description of an album. + */ +class EditAlbumPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("editAlbum.html", template, "Page.EditAlbum.Title", webInterface, true) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + if (request.isPOST) { + val album = webInterface.core.getAlbum(request.httpRequest.getPartAsStringFailsafe("album", 36)) ?: throw RedirectException("invalid.html") + album.takeUnless { it.sone.isLocal }?.run { throw RedirectException("noPermission.html") } + if (request.httpRequest.getPartAsStringFailsafe("moveLeft", 4) == "true") { + album.parent?.moveAlbumUp(album) + webInterface.core.touchConfiguration() + throw RedirectException("imageBrowser.html?album=${album.parent?.id}") + } else if (request.httpRequest.getPartAsStringFailsafe("moveRight", 4) == "true") { + album.parent?.moveAlbumDown(album) + webInterface.core.touchConfiguration() + throw RedirectException("imageBrowser.html?album=${album.parent?.id}") + } else { + try { + album.modify() + .setTitle(request.httpRequest.getPartAsStringFailsafe("title", 100)) + .setDescription(request.httpRequest.getPartAsStringFailsafe("description", 1000)) + .update() + } catch (e: AlbumTitleMustNotBeEmpty) { + throw RedirectException("emptyAlbumTitle.html") + } + webInterface.core.touchConfiguration() + throw RedirectException("imageBrowser.html?album=${album.id}") + } + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/LoginPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/LoginPage.kt new file mode 100644 index 0000000..695816f --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/LoginPage.kt @@ -0,0 +1,39 @@ +package net.pterodactylus.sone.web.pages + +import freenet.clients.http.ToadletContext +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.utils.isPOST +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.template.Template +import net.pterodactylus.util.template.TemplateContext + +/** + * The login page lets the user log in. + */ +class LoginPage(template: Template, webInterface: WebInterface): + SoneTemplatePage("login.html", template, "Page.Login.Title", webInterface) { + + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + if (request.isPOST) { + val soneId = request.httpRequest.getPartAsStringFailsafe("sone-id", 43) + webInterface.core.getLocalSone(soneId)?.let { sone -> + setCurrentSone(request.toadletContext, sone) + val target = if (request.httpRequest.isParameterSet("target")) request.httpRequest.getPartAsStringFailsafe("target", 256) else "index.html" + throw RedirectException(target) + } + } + templateContext["sones"] = webInterface.core.localSones.sortedWith(Sone.NICE_NAME_COMPARATOR) + templateContext["identitiesWithoutSone"] = webInterface.core.identityManager.allOwnIdentities.filterNot { "Sone" in it.contexts }.sortedBy { "${it.nickname}@${it.id}" } + } + + override public fun getRedirectTarget(request: FreenetRequest) = + getCurrentSone(request.toadletContext)?.let { "index.html" } + + override fun isEnabled(toadletContext: ToadletContext) = when { + webInterface.core.preferences.isRequireFullAccess && !toadletContext.isAllowedFullAccess -> false + else -> getCurrentSoneWithoutCreatingSession(toadletContext) == null + } + +} diff --git a/src/test/java/net/pterodactylus/sone/web/CreateAlbumPageTest.kt b/src/test/java/net/pterodactylus/sone/web/CreateAlbumPageTest.kt deleted file mode 100644 index 054917d..0000000 --- a/src/test/java/net/pterodactylus/sone/web/CreateAlbumPageTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty -import net.pterodactylus.sone.test.deepMock -import net.pterodactylus.sone.test.selfMock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [CreateAlbumPage]. - */ -class CreateAlbumPageTest: WebPageTest() { - - private val page = CreateAlbumPage(template, webInterface) - - override fun getPage() = page - - private val parentAlbum = createAlbum("parent-id") - private val newAlbum = createAlbum("album-id") - - @Before - fun setupAlbums() { - whenever(core.createAlbum(currentSone, parentAlbum)).thenReturn(newAlbum) - whenever(currentSone.rootAlbum).thenReturn(parentAlbum) - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("createAlbum.html")) - } - - @Test - fun `get request shows template`() { - page.processTemplate(freenetRequest, templateContext) - } - - @Test - fun `missing name results in attribute being set in template context`() { - request("", POST) - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["nameMissing"], equalTo(true)) - } - - private fun createAlbum(albumId: String) = deepMock().apply { - whenever(id).thenReturn(albumId) - selfMock().let { modifier -> - whenever(modifier.update()).thenReturn(this@apply) - whenever(this@apply.modify()).thenReturn(modifier) - } - } - - @Test - fun `title and description are set correctly on the album`() { - request("", POST) - addAlbum("parent-id", parentAlbum) - addHttpRequestParameter("name", "new name") - addHttpRequestParameter("description", "new description") - addHttpRequestParameter("parent", "parent-id") - verifyRedirect("imageBrowser.html?album=album-id") { - verify(newAlbum).modify() - verify(newAlbum.modify()).setTitle("new name") - verify(newAlbum.modify()).setDescription("new description") - verify(newAlbum.modify()).update() - verify(core).touchConfiguration() - } - } - - @Test - fun `root album is used if no parent is specified`() { - request("", POST) - addHttpRequestParameter("name", "new name") - addHttpRequestParameter("description", "new description") - verifyRedirect("imageBrowser.html?album=album-id") - } - - @Test - fun `empty album title redirects to error page`() { - request("", POST) - whenever(newAlbum.modify().update()).thenThrow(AlbumTitleMustNotBeEmpty::class.java) - addHttpRequestParameter("name", "new name") - addHttpRequestParameter("description", "new description") - verifyRedirect("emptyAlbumTitle.html") - } - - @Test - fun `album description is filtered`() { - request("", POST) - addHttpRequestParameter("name", "new name") - addHttpRequestParameter("description", "new http://localhost:12345/KSK@foo description") - addHttpRequestHeader("Host", "localhost:12345") - verifyRedirect("imageBrowser.html?album=album-id") { - verify(newAlbum.modify()).setDescription("new KSK@foo description") - } - } - -} diff --git a/src/test/java/net/pterodactylus/sone/web/CreateReplyPageTest.kt b/src/test/java/net/pterodactylus/sone/web/CreateReplyPageTest.kt deleted file mode 100644 index dba49a8..0000000 --- a/src/test/java/net/pterodactylus/sone/web/CreateReplyPageTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [CreateReplyPage]. - */ -class CreateReplyPageTest: WebPageTest() { - - private val page = CreateReplyPage(template, webInterface) - override fun getPage() = page - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("createReply.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `reply is created correctly`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("text", "new text") - val post = mock().apply { addPost("post-id", this) } - verifyRedirect("return.html") { - verify(core).createReply(currentSone, post, "new text") - } - } - - @Test - fun `reply is filtered`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("text", "new http://localhost:12345/KSK@foo text") - addHttpRequestHeader("Host", "localhost:12345") - val post = mock().apply { addPost("post-id", this) } - verifyRedirect("return.html") { - verify(core).createReply(currentSone, post, "new KSK@foo text") - } - } - - @Test - fun `reply is created with correct sender`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("text", "new text") - addHttpRequestParameter("sender", "sender-id") - val sender = mock().apply { addLocalSone("sender-id", this) } - val post = mock().apply { addPost("post-id", this) } - verifyRedirect("return.html") { - verify(core).createReply(sender, post, "new text") - } - } - - @Test - fun `empty text sets parameters in template contexty`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("text", " ") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["errorTextEmpty"], equalTo(true)) - assertThat(templateContext["returnPage"], equalTo("return.html")) - assertThat(templateContext["postId"], equalTo("post-id")) - assertThat(templateContext["text"], equalTo("")) - } - - @Test - fun `user is redirected to no permissions page if post does not exist`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("text", "new text") - verifyRedirect("noPermission.html") - } - - @Test - fun `get request stores parameters in template context`() { - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("text", "new text") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["returnPage"], equalTo("return.html")) - assertThat(templateContext["postId"], equalTo("post-id")) - assertThat(templateContext["text"], equalTo("new text")) - } - -} diff --git a/src/test/java/net/pterodactylus/sone/web/NewPageTest.java b/src/test/java/net/pterodactylus/sone/web/NewPageTest.java deleted file mode 100644 index 223fcb2..0000000 --- a/src/test/java/net/pterodactylus/sone/web/NewPageTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.pterodactylus.sone.web; - -import static java.util.Arrays.asList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.List; - -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.PostReply; - -import com.google.common.base.Optional; -import org.junit.Before; -import org.junit.Test; - -/** - * Unit test for {@link NewPage}. - * - * @author David ‘Bombe’ Roden - */ -public class NewPageTest extends WebPageTest { - - private final NewPage newPage = new NewPage(template, webInterface); - - @Before - public void setupNumberOfPostsPerPage() { - webInterface.getCore().getPreferences().setPostsPerPage(5); - } - - @Test - public void postsAreNotDuplicatedWhenTheyComeFromBothNewPostsAndNewRepliesNotifications() throws Exception { - // given - Post extraPost = mock(Post.class); - List posts = asList(mock(Post.class), mock(Post.class)); - List postReplies = asList(mock(PostReply.class), mock(PostReply.class)); - when(postReplies.get(0).getPost()).thenReturn(Optional.of(posts.get(0))); - when(postReplies.get(1).getPost()).thenReturn(Optional.of(extraPost)); - when(webInterface.getNewPosts(currentSone)).thenReturn(posts); - when(webInterface.getNewReplies(currentSone)).thenReturn(postReplies); - - // when - newPage.processTemplate(freenetRequest, templateContext); - - // then - List renderedPosts = templateContext.get("posts", List.class); - assertThat(renderedPosts, containsInAnyOrder(posts.get(0), posts.get(1), extraPost)); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/web/WebPageTest.java b/src/test/java/net/pterodactylus/sone/web/WebPageTest.java deleted file mode 100644 index 6f99879..0000000 --- a/src/test/java/net/pterodactylus/sone/web/WebPageTest.java +++ /dev/null @@ -1,384 +0,0 @@ -package net.pterodactylus.sone.web; - -import static net.pterodactylus.sone.test.GuiceKt.supply; -import static net.pterodactylus.sone.web.WebTestUtils.redirectsTo; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.Nonnull; - -import net.pterodactylus.sone.core.Core; -import net.pterodactylus.sone.core.Preferences; -import net.pterodactylus.sone.core.UpdateChecker; -import net.pterodactylus.sone.data.Album; -import net.pterodactylus.sone.data.Image; -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.PostReply; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions; -import net.pterodactylus.sone.data.TemporaryImage; -import net.pterodactylus.sone.freenet.wot.OwnIdentity; -import net.pterodactylus.sone.web.page.FreenetRequest; -import net.pterodactylus.sone.web.page.FreenetTemplatePage.RedirectException; -import net.pterodactylus.util.notify.Notification; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; -import net.pterodactylus.util.web.Method; -import net.pterodactylus.util.web.Response; - -import freenet.clients.http.ToadletContext; -import freenet.l10n.BaseL10n; -import freenet.support.SimpleReadOnlyArrayBucket; -import freenet.support.api.Bucket; -import freenet.support.api.HTTPRequest; -import freenet.support.api.HTTPUploadedFile; -import freenet.support.io.NullBucket; - -import com.google.common.base.Optional; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Multimap; -import com.google.common.eventbus.EventBus; -import com.google.common.io.ByteStreams; -import com.google.inject.Guice; -import com.google.inject.Injector; -import org.junit.Before; -import org.junit.Rule; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentMatchers; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -/** - * Base class for web page tests. - * - * @author David ‘Bombe’ Roden - */ -public abstract class WebPageTest { - - @Rule - public final ExpectedException expectedException = ExpectedException.none(); - - protected final Template template = new Template(); - protected final WebInterface webInterface = mock(WebInterface.class, RETURNS_DEEP_STUBS); - protected final EventBus eventBus = mock(EventBus.class); - protected final Core core = webInterface.getCore(); - protected final BaseL10n l10n = webInterface.getL10n(); - - protected final Sone currentSone = mock(Sone.class); - - protected final TemplateContext templateContext = new TemplateContext(); - protected final HTTPRequest httpRequest = mock(HTTPRequest.class); - protected final Multimap requestParameters = ArrayListMultimap.create(); - protected final Map requestHeaders = new HashMap<>(); - private final Map uploadedFilesNames = new HashMap<>(); - private final Map uploadedFilesContentTypes = new HashMap<>(); - private final Map uploadedFilesSources = new HashMap<>(); - protected final FreenetRequest freenetRequest = mock(FreenetRequest.class); - private final PipedOutputStream responseOutputStream = new PipedOutputStream(); - private final PipedInputStream responseInputStream; - protected final Response response = new Response(responseOutputStream); - protected final ToadletContext toadletContext = mock(ToadletContext.class); - - private final Set ownIdentities = new HashSet<>(); - private final Map sones = new HashMap<>(); - protected final List localSones = new ArrayList<>(); - private final ListMultimap postReplies = ArrayListMultimap.create(); - - protected final Injector injector = Guice.createInjector( - supply(WebInterface.class).byInstance(webInterface), - supply(Template.class).byInstance(template) - ); - - protected WebPageTest() { - try { - responseInputStream = new PipedInputStream(responseOutputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Before - public final void setupFreenetRequest() { - when(freenetRequest.getToadletContext()).thenReturn(toadletContext); - when(freenetRequest.getHttpRequest()).thenReturn(httpRequest); - when(httpRequest.getMultipleParam(anyString())).thenAnswer(new Answer() { - @Override - public String[] answer(InvocationOnMock invocation) throws Throwable { - return requestParameters.get(invocation.getArgument(0)).toArray(new String[0]); - } - }); - when(httpRequest.getPartAsStringFailsafe(anyString(), anyInt())).thenAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - String parameter = invocation.getArgument(0); - int maxLength = invocation.getArgument(1); - Collection values = requestParameters.get(parameter); - return requestParameters.containsKey(parameter) ? values.iterator().next().substring(0, Math.min(maxLength, values.iterator().next().length())) : ""; - } - }); - when(httpRequest.hasParameters()).thenAnswer(new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - return !requestParameters.isEmpty(); - } - }); - when(httpRequest.getParameterNames()).thenAnswer(new Answer>() { - @Override - public Collection answer(InvocationOnMock invocation) throws Throwable { - return requestParameters.keySet(); - } - }); - when(httpRequest.getParam(anyString())).thenAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - String parameter = invocation.getArgument(0); - return requestParameters.containsKey(parameter) ? requestParameters.get(parameter).iterator().next() : ""; - } - }); - when(httpRequest.getParam(anyString(), ArgumentMatchers.any())).thenAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - String parameter = invocation.getArgument(0); - return requestParameters.containsKey(parameter) ? requestParameters.get(parameter).iterator().next() : invocation.getArgument(1); - } - }); - when(httpRequest.isParameterSet(anyString())).thenAnswer(new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - return requestParameters.containsKey(invocation.getArgument(0)) && - requestParameters.get(invocation.getArgument(0)).iterator().next() != null; - } - }); - when(httpRequest.isPartSet(anyString())).thenAnswer(new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - return requestParameters.containsKey(invocation.getArgument(0)) && - requestParameters.get(invocation.getArgument(0)).iterator().next() != null; - } - }); - when(httpRequest.getParts()).thenAnswer(new Answer() { - @Override - public String[] answer(InvocationOnMock invocation) throws Throwable { - return requestParameters.keySet().toArray(new String[requestParameters.size()]); - } - }); - when(httpRequest.getHeader(anyString())).thenAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - return requestHeaders.get(invocation.getArgument(0).toLowerCase()); - } - }); - when(httpRequest.getUploadedFile(anyString())).thenAnswer(new Answer() { - @Override - public HTTPUploadedFile answer(InvocationOnMock invocation) throws Throwable { - final String name = invocation.getArgument(0); - if (!uploadedFilesSources.containsKey(name)) { - return null; - } - return new HTTPUploadedFile() { - @Override - public String getContentType() { - return uploadedFilesContentTypes.get(name); - } - - @Override - public Bucket getData() { - try (InputStream inputStream = getClass().getResourceAsStream(uploadedFilesSources.get(name))) { - byte[] bytes = ByteStreams.toByteArray(inputStream); - return new SimpleReadOnlyArrayBucket(bytes, 0, bytes.length); - } catch (IOException ioe1) { - return new NullBucket(); - } - } - - @Override - public String getFilename() { - return uploadedFilesNames.get(name); - } - }; - } - }); - } - - @Before - public final void setupCore() { - UpdateChecker updateChecker = mock(UpdateChecker.class); - when(core.getUpdateChecker()).thenReturn(updateChecker); - when(core.getPreferences()).thenReturn(new Preferences(eventBus)); - when(core.getLocalSone(anyString())).thenReturn(null); - when(core.getLocalSones()).thenReturn(localSones); - when(core.getSone(anyString())).thenReturn(Optional.absent()); - when(core.getSones()).thenAnswer(new Answer>() { - @Override - public Collection answer(InvocationOnMock invocation) throws Throwable { - return sones.values(); - } - }); - when(core.getSone(anyString())).thenAnswer(new Answer>() { - @Override - public Optional answer(InvocationOnMock invocation) throws Throwable { - return Optional.fromNullable(sones.get(invocation.getArgument(0))); - } - }); - when(core.getPost(anyString())).thenReturn(Optional.absent()); - when(core.getPostReply(anyString())).thenReturn(Optional.absent()); - when(core.getReplies(anyString())).thenAnswer(new Answer>() { - @Override - public List answer(InvocationOnMock invocation) throws Throwable { - return postReplies.get(invocation.getArgument(0)); - } - }); - when(core.getAlbum(anyString())).thenReturn(null); - when(core.getImage(anyString())).thenReturn(null); - when(core.getImage(anyString(), anyBoolean())).thenReturn(null); - when(core.getTemporaryImage(anyString())).thenReturn(null); - } - - @Before - public void setupL10n() { - when(l10n.getString(anyString())).thenAnswer(new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - return invocation.getArgument(0); - } - }); - } - - @Before - public final void setupIdentityManager() { - when(core.getIdentityManager().getAllOwnIdentities()).thenReturn(ownIdentities); - } - - @Before - public final void setupWebInterface() { - when(webInterface.getCurrentSoneCreatingSession(toadletContext)).thenReturn(currentSone); - when(webInterface.getCurrentSoneWithoutCreatingSession(toadletContext)).thenReturn(currentSone); - when(webInterface.getNotification(anyString())).thenReturn(Optional.absent()); - when(webInterface.getNotifications(currentSone)).thenReturn(new ArrayList()); - } - - @Before - public void setupSone() { - when(currentSone.getOptions()).thenReturn(new DefaultSoneOptions()); - } - - protected SoneTemplatePage getPage() { - return null; - } - - protected void unsetCurrentSone() { - when(webInterface.getCurrentSoneCreatingSession(toadletContext)).thenReturn(null); - when(webInterface.getCurrentSoneWithoutCreatingSession(toadletContext)).thenReturn(null); - } - - protected void request(String uri, Method method) { - try { - when(httpRequest.getPath()).thenReturn(uri); - when(freenetRequest.getUri()).thenReturn(new URI(uri)); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - when(freenetRequest.getMethod()).thenReturn(method); - } - - protected void addHttpRequestHeader(@Nonnull String name, String value) { - requestHeaders.put(name.toLowerCase(), value); - } - - protected void addHttpRequestParameter(String name, final String value) { - requestParameters.put(name, value); - } - - protected void addPost(String postId, Post post) { - when(core.getPost(postId)).thenReturn(Optional.fromNullable(post)); - } - - protected void addPostReply(String postReplyId, PostReply postReply) { - if (postReply.getPostId() != null) { - postReplies.put(postReply.getPostId(), postReply); - } - when(core.getPostReply(postReplyId)).thenReturn(Optional.fromNullable(postReply)); - } - - protected void addSone(String soneId, Sone sone) { - sones.put(soneId, sone); - } - - protected void addLocalSone(String soneId, Sone sone) { - when(core.getLocalSone(eq(soneId))).thenReturn(sone); - localSones.add(sone); - } - - protected void addOwnIdentity(OwnIdentity ownIdentity) { - ownIdentities.add(ownIdentity); - } - - protected void addAlbum(String albumId, Album album) { - when(core.getAlbum(eq(albumId))).thenReturn(album); - } - - protected void addImage(String imageId, Image image) { - when(core.getImage(eq(imageId))).thenReturn(image); - when(core.getImage(eq(imageId), anyBoolean())).thenReturn(image); - } - - protected void addTemporaryImage(String imageId, TemporaryImage temporaryImage) { - when(core.getTemporaryImage(eq(imageId))).thenReturn(temporaryImage); - } - - protected void addUploadedFile(@Nonnull String name, @Nonnull String filename, @Nonnull String contentType, @Nonnull String resource) { - uploadedFilesNames.put(name, filename); - uploadedFilesContentTypes.put(name, contentType); - uploadedFilesSources.put(name, resource); - } - - protected byte[] getResponseBytes() throws IOException { - response.getContent().close(); - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - ByteStreams.copy(responseInputStream, outputStream); - return outputStream.toByteArray(); - } - } - - protected void addNotification(String notificationId, Notification notification) { - when(webInterface.getNotification(eq(notificationId))).thenReturn(Optional.of(notification)); - } - - protected void verifyRedirect(String target) throws RedirectException { - expectedException.expect(redirectsTo(target)); - getPage().handleRequest(freenetRequest, templateContext); - } - - protected void verifyRedirect(String target, Runnable verification) throws RedirectException { - expectedException.expect(redirectsTo(target)); - try { - getPage().handleRequest(freenetRequest, templateContext); - fail(); - } finally { - verification.run(); - } - } - -} diff --git a/src/test/java/net/pterodactylus/sone/web/pages/NewPageTest.java b/src/test/java/net/pterodactylus/sone/web/pages/NewPageTest.java new file mode 100644 index 0000000..2811e8b --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/web/pages/NewPageTest.java @@ -0,0 +1,51 @@ +package net.pterodactylus.sone.web.pages; + +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; + +import com.google.common.base.Optional; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit test for {@link NewPage}. + * + * @author David ‘Bombe’ Roden + */ +public class NewPageTest extends WebPageTest { + + private final NewPage newPage = new NewPage(template, webInterface); + + @Before + public void setupNumberOfPostsPerPage() { + webInterface.getCore().getPreferences().setPostsPerPage(5); + } + + @Test + public void postsAreNotDuplicatedWhenTheyComeFromBothNewPostsAndNewRepliesNotifications() throws Exception { + // given + Post extraPost = mock(Post.class); + List posts = asList(mock(Post.class), mock(Post.class)); + List postReplies = asList(mock(PostReply.class), mock(PostReply.class)); + when(postReplies.get(0).getPost()).thenReturn(Optional.of(posts.get(0))); + when(postReplies.get(1).getPost()).thenReturn(Optional.of(extraPost)); + when(webInterface.getNewPosts(currentSone)).thenReturn(posts); + when(webInterface.getNewReplies(currentSone)).thenReturn(postReplies); + + // when + newPage.processTemplate(freenetRequest, templateContext); + + // then + List renderedPosts = templateContext.get("posts", List.class); + assertThat(renderedPosts, containsInAnyOrder(posts.get(0), posts.get(1), extraPost)); + } + +} diff --git a/src/test/java/net/pterodactylus/sone/web/pages/WebPageTest.java b/src/test/java/net/pterodactylus/sone/web/pages/WebPageTest.java new file mode 100644 index 0000000..3c6b1d4 --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/web/pages/WebPageTest.java @@ -0,0 +1,385 @@ +package net.pterodactylus.sone.web.pages; + +import static net.pterodactylus.sone.test.GuiceKt.supply; +import static net.pterodactylus.sone.web.WebTestUtils.redirectsTo; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nonnull; + +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.core.Preferences; +import net.pterodactylus.sone.core.UpdateChecker; +import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Image; +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions; +import net.pterodactylus.sone.data.TemporaryImage; +import net.pterodactylus.sone.freenet.wot.OwnIdentity; +import net.pterodactylus.sone.web.WebInterface; +import net.pterodactylus.sone.web.page.FreenetRequest; +import net.pterodactylus.sone.web.page.FreenetTemplatePage.RedirectException; +import net.pterodactylus.util.notify.Notification; +import net.pterodactylus.util.template.Template; +import net.pterodactylus.util.template.TemplateContext; +import net.pterodactylus.util.web.Method; +import net.pterodactylus.util.web.Response; + +import freenet.clients.http.ToadletContext; +import freenet.l10n.BaseL10n; +import freenet.support.SimpleReadOnlyArrayBucket; +import freenet.support.api.Bucket; +import freenet.support.api.HTTPRequest; +import freenet.support.api.HTTPUploadedFile; +import freenet.support.io.NullBucket; + +import com.google.common.base.Optional; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.eventbus.EventBus; +import com.google.common.io.ByteStreams; +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentMatchers; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** + * Base class for web page tests. + * + * @author David ‘Bombe’ Roden + */ +public abstract class WebPageTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + protected final Template template = new Template(); + protected final WebInterface webInterface = mock(WebInterface.class, RETURNS_DEEP_STUBS); + protected final EventBus eventBus = mock(EventBus.class); + protected final Core core = webInterface.getCore(); + protected final BaseL10n l10n = webInterface.getL10n(); + + protected final Sone currentSone = mock(Sone.class); + + protected final TemplateContext templateContext = new TemplateContext(); + protected final HTTPRequest httpRequest = mock(HTTPRequest.class); + protected final Multimap requestParameters = ArrayListMultimap.create(); + protected final Map requestHeaders = new HashMap<>(); + private final Map uploadedFilesNames = new HashMap<>(); + private final Map uploadedFilesContentTypes = new HashMap<>(); + private final Map uploadedFilesSources = new HashMap<>(); + protected final FreenetRequest freenetRequest = mock(FreenetRequest.class); + private final PipedOutputStream responseOutputStream = new PipedOutputStream(); + private final PipedInputStream responseInputStream; + protected final Response response = new Response(responseOutputStream); + protected final ToadletContext toadletContext = mock(ToadletContext.class); + + private final Set ownIdentities = new HashSet<>(); + private final Map sones = new HashMap<>(); + protected final List localSones = new ArrayList<>(); + private final ListMultimap postReplies = ArrayListMultimap.create(); + + protected final Injector injector = Guice.createInjector( + supply(WebInterface.class).byInstance(webInterface), + supply(Template.class).byInstance(template) + ); + + protected WebPageTest() { + try { + responseInputStream = new PipedInputStream(responseOutputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Before + public final void setupFreenetRequest() { + when(freenetRequest.getToadletContext()).thenReturn(toadletContext); + when(freenetRequest.getHttpRequest()).thenReturn(httpRequest); + when(httpRequest.getMultipleParam(anyString())).thenAnswer(new Answer() { + @Override + public String[] answer(InvocationOnMock invocation) throws Throwable { + return requestParameters.get(invocation.getArgument(0)).toArray(new String[0]); + } + }); + when(httpRequest.getPartAsStringFailsafe(anyString(), anyInt())).thenAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + String parameter = invocation.getArgument(0); + int maxLength = invocation.getArgument(1); + Collection values = requestParameters.get(parameter); + return requestParameters.containsKey(parameter) ? values.iterator().next().substring(0, Math.min(maxLength, values.iterator().next().length())) : ""; + } + }); + when(httpRequest.hasParameters()).thenAnswer(new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return !requestParameters.isEmpty(); + } + }); + when(httpRequest.getParameterNames()).thenAnswer(new Answer>() { + @Override + public Collection answer(InvocationOnMock invocation) throws Throwable { + return requestParameters.keySet(); + } + }); + when(httpRequest.getParam(anyString())).thenAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + String parameter = invocation.getArgument(0); + return requestParameters.containsKey(parameter) ? requestParameters.get(parameter).iterator().next() : ""; + } + }); + when(httpRequest.getParam(anyString(), ArgumentMatchers.any())).thenAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + String parameter = invocation.getArgument(0); + return requestParameters.containsKey(parameter) ? requestParameters.get(parameter).iterator().next() : invocation.getArgument(1); + } + }); + when(httpRequest.isParameterSet(anyString())).thenAnswer(new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return requestParameters.containsKey(invocation.getArgument(0)) && + requestParameters.get(invocation.getArgument(0)).iterator().next() != null; + } + }); + when(httpRequest.isPartSet(anyString())).thenAnswer(new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return requestParameters.containsKey(invocation.getArgument(0)) && + requestParameters.get(invocation.getArgument(0)).iterator().next() != null; + } + }); + when(httpRequest.getParts()).thenAnswer(new Answer() { + @Override + public String[] answer(InvocationOnMock invocation) throws Throwable { + return requestParameters.keySet().toArray(new String[requestParameters.size()]); + } + }); + when(httpRequest.getHeader(anyString())).thenAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + return requestHeaders.get(invocation.getArgument(0).toLowerCase()); + } + }); + when(httpRequest.getUploadedFile(anyString())).thenAnswer(new Answer() { + @Override + public HTTPUploadedFile answer(InvocationOnMock invocation) throws Throwable { + final String name = invocation.getArgument(0); + if (!uploadedFilesSources.containsKey(name)) { + return null; + } + return new HTTPUploadedFile() { + @Override + public String getContentType() { + return uploadedFilesContentTypes.get(name); + } + + @Override + public Bucket getData() { + try (InputStream inputStream = getClass().getResourceAsStream(uploadedFilesSources.get(name))) { + byte[] bytes = ByteStreams.toByteArray(inputStream); + return new SimpleReadOnlyArrayBucket(bytes, 0, bytes.length); + } catch (IOException ioe1) { + return new NullBucket(); + } + } + + @Override + public String getFilename() { + return uploadedFilesNames.get(name); + } + }; + } + }); + } + + @Before + public final void setupCore() { + UpdateChecker updateChecker = mock(UpdateChecker.class); + when(core.getUpdateChecker()).thenReturn(updateChecker); + when(core.getPreferences()).thenReturn(new Preferences(eventBus)); + when(core.getLocalSone(anyString())).thenReturn(null); + when(core.getLocalSones()).thenReturn(localSones); + when(core.getSone(anyString())).thenReturn(Optional.absent()); + when(core.getSones()).thenAnswer(new Answer>() { + @Override + public Collection answer(InvocationOnMock invocation) throws Throwable { + return sones.values(); + } + }); + when(core.getSone(anyString())).thenAnswer(new Answer>() { + @Override + public Optional answer(InvocationOnMock invocation) throws Throwable { + return Optional.fromNullable(sones.get(invocation.getArgument(0))); + } + }); + when(core.getPost(anyString())).thenReturn(Optional.absent()); + when(core.getPostReply(anyString())).thenReturn(Optional.absent()); + when(core.getReplies(anyString())).thenAnswer(new Answer>() { + @Override + public List answer(InvocationOnMock invocation) throws Throwable { + return postReplies.get(invocation.getArgument(0)); + } + }); + when(core.getAlbum(anyString())).thenReturn(null); + when(core.getImage(anyString())).thenReturn(null); + when(core.getImage(anyString(), anyBoolean())).thenReturn(null); + when(core.getTemporaryImage(anyString())).thenReturn(null); + } + + @Before + public void setupL10n() { + when(l10n.getString(anyString())).thenAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + return invocation.getArgument(0); + } + }); + } + + @Before + public final void setupIdentityManager() { + when(core.getIdentityManager().getAllOwnIdentities()).thenReturn(ownIdentities); + } + + @Before + public final void setupWebInterface() { + when(webInterface.getCurrentSoneCreatingSession(toadletContext)).thenReturn(currentSone); + when(webInterface.getCurrentSoneWithoutCreatingSession(toadletContext)).thenReturn(currentSone); + when(webInterface.getNotification(anyString())).thenReturn(Optional.absent()); + when(webInterface.getNotifications(currentSone)).thenReturn(new ArrayList()); + } + + @Before + public void setupSone() { + when(currentSone.getOptions()).thenReturn(new DefaultSoneOptions()); + } + + protected SoneTemplatePage getPage() { + return null; + } + + protected void unsetCurrentSone() { + when(webInterface.getCurrentSoneCreatingSession(toadletContext)).thenReturn(null); + when(webInterface.getCurrentSoneWithoutCreatingSession(toadletContext)).thenReturn(null); + } + + protected void request(String uri, Method method) { + try { + when(httpRequest.getPath()).thenReturn(uri); + when(freenetRequest.getUri()).thenReturn(new URI(uri)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + when(freenetRequest.getMethod()).thenReturn(method); + } + + protected void addHttpRequestHeader(@Nonnull String name, String value) { + requestHeaders.put(name.toLowerCase(), value); + } + + protected void addHttpRequestParameter(String name, final String value) { + requestParameters.put(name, value); + } + + protected void addPost(String postId, Post post) { + when(core.getPost(postId)).thenReturn(Optional.fromNullable(post)); + } + + protected void addPostReply(String postReplyId, PostReply postReply) { + if (postReply.getPostId() != null) { + postReplies.put(postReply.getPostId(), postReply); + } + when(core.getPostReply(postReplyId)).thenReturn(Optional.fromNullable(postReply)); + } + + protected void addSone(String soneId, Sone sone) { + sones.put(soneId, sone); + } + + protected void addLocalSone(String soneId, Sone sone) { + when(core.getLocalSone(eq(soneId))).thenReturn(sone); + localSones.add(sone); + } + + protected void addOwnIdentity(OwnIdentity ownIdentity) { + ownIdentities.add(ownIdentity); + } + + protected void addAlbum(String albumId, Album album) { + when(core.getAlbum(eq(albumId))).thenReturn(album); + } + + protected void addImage(String imageId, Image image) { + when(core.getImage(eq(imageId))).thenReturn(image); + when(core.getImage(eq(imageId), anyBoolean())).thenReturn(image); + } + + protected void addTemporaryImage(String imageId, TemporaryImage temporaryImage) { + when(core.getTemporaryImage(eq(imageId))).thenReturn(temporaryImage); + } + + protected void addUploadedFile(@Nonnull String name, @Nonnull String filename, @Nonnull String contentType, @Nonnull String resource) { + uploadedFilesNames.put(name, filename); + uploadedFilesContentTypes.put(name, contentType); + uploadedFilesSources.put(name, resource); + } + + protected byte[] getResponseBytes() throws IOException { + response.getContent().close(); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + ByteStreams.copy(responseInputStream, outputStream); + return outputStream.toByteArray(); + } + } + + protected void addNotification(String notificationId, Notification notification) { + when(webInterface.getNotification(eq(notificationId))).thenReturn(Optional.of(notification)); + } + + protected void verifyRedirect(String target) throws RedirectException { + expectedException.expect(redirectsTo(target)); + getPage().handleRequest(freenetRequest, templateContext); + } + + protected void verifyRedirect(String target, Runnable verification) throws RedirectException { + expectedException.expect(redirectsTo(target)); + try { + getPage().handleRequest(freenetRequest, templateContext); + fail(); + } finally { + verification.run(); + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/AboutPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/AboutPageTest.kt deleted file mode 100644 index d4adf3a..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/AboutPageTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.main.SonePlugin.PluginHomepage -import net.pterodactylus.sone.main.SonePlugin.PluginVersion -import net.pterodactylus.sone.main.SonePlugin.PluginYear -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test - -/** - * Unit test for [AboutPage]. - */ -class AboutPageTest: WebPageTest() { - - private val version = "0.1.2" - private val year = 1234 - private val homepage = "home://page" - private val page = AboutPage(template, webInterface, PluginVersion(version), PluginYear(year), PluginHomepage(homepage)) - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("about.html")) - } - - @Test - fun `page does not require login`() { - assertThat(page.requiresLogin(), equalTo(false)) - } - - @Test - fun `page sets correct version in template context`() { - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["version"], equalTo(version)) - } - - @Test - fun `page sets correct homepage in template context`() { - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["homepage"], equalTo(homepage)) - } - - @Test - fun `page sets correct year in template context`() { - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["year"], equalTo(year)) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/BookmarkPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/BookmarkPageTest.kt deleted file mode 100644 index bebe10c..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/BookmarkPageTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.test.mock -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [BookmarkPage]. - */ -class BookmarkPageTest : WebPageTest() { - - private val page = BookmarkPage(template, webInterface) - override fun getPage() = page - - @Test - fun `path is set correctly`() { - assertThat(page.path, equalTo("bookmark.html")) - } - - @Test - fun `get request does not bookmark anything and does not redirect`() { - page.processTemplate(freenetRequest, templateContext) - verify(core, never()).bookmarkPost(any()) - } - - private fun setupBookmarkRequest() { - request("", POST) - addHttpRequestParameter("returnPage", "return-page.html") - addHttpRequestParameter("post", "post-id") - } - - @Test - fun `post is bookmarked correctly`() { - setupBookmarkRequest() - val post = mock() - addPost("post-id", post) - verifyRedirect("return-page.html") { - verify(core).bookmarkPost(post) - } - } - - @Test - fun `non-existing post is not bookmarked`() { - setupBookmarkRequest() - verifyRedirect("return-page.html") { - verify(core, never()).bookmarkPost(any()) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/BookmarksPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/BookmarksPageTest.kt deleted file mode 100644 index cfde866..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/BookmarksPageTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.collection.Pagination -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test - -/** - * Unit test for [BookmarksPage]. - */ -class BookmarksPageTest: WebPageTest() { - - private val page = BookmarksPage(template, webInterface) - private val post1 = createLoadedPost(1000) - private val post2 = createLoadedPost(3000) - private val post3 = createLoadedPost(2000) - - private fun createLoadedPost(time: Long) = mock().apply { - whenever(isLoaded).thenReturn(true) - whenever(this.time).thenReturn(time) - } - - @Before - fun setupBookmarkedPostsAndPagination() { - whenever(core.bookmarkedPosts).thenReturn(setOf(post1, post2, post3)) - core.preferences.postsPerPage = 5 - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("bookmarks.html")) - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `page sets correct posts in template context`() { - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["posts"] as Collection, contains(post2, post3, post1)) - assertThat((templateContext["pagination"] as Pagination).items, contains(post2, post3, post1)) - assertThat(templateContext["postsNotLoaded"], equalTo(false)) - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `page does not put unloaded posts in template context but sets a flag`() { - whenever(post3.isLoaded).thenReturn(false) - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["posts"] as Collection, contains(post2, post1)) - assertThat((templateContext["pagination"] as Pagination).items, contains(post2, post1)) - assertThat(templateContext["postsNotLoaded"], equalTo(true)) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/CreatePostPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/CreatePostPageTest.kt deleted file mode 100644 index f1a120e..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/CreatePostPageTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package net.pterodactylus.sone.web - -import com.google.common.base.Optional.absent -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.asOptional -import net.pterodactylus.sone.test.mock -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [CreatePostPage]. - */ -class CreatePostPageTest: WebPageTest() { - - private val page = CreatePostPage(template, webInterface) - - override fun getPage() = page - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("createPost.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `return page is set in template context`() { - addHttpRequestParameter("returnPage", "return.html") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["returnPage"], equalTo("return.html")) - } - - @Test - fun `post is created correctly`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("text", "post text") - verifyRedirect("return.html") { - verify(core).createPost(currentSone, absent(), "post text") - } - } - - @Test - fun `creating an empty post is denied`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("text", " ") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["errorTextEmpty"], equalTo(true)) - } - - @Test - fun `a sender can be selected`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("text", "post text") - addHttpRequestParameter("sender", "sender-id") - val sender = mock() - addLocalSone("sender-id", sender) - verifyRedirect("return.html") { - verify(core).createPost(sender, absent(), "post text") - } - } - - @Test - fun `a recipient can be selected`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("text", "post text") - addHttpRequestParameter("recipient", "recipient-id") - val recipient = mock() - addSone("recipient-id", recipient) - verifyRedirect("return.html") { - verify(core).createPost(currentSone, recipient.asOptional(), "post text") - } - } - - @Test - fun `text is filtered correctly`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("text", "post http://localhost:12345/KSK@foo text") - addHttpRequestHeader("Host", "localhost:12345") - verifyRedirect("return.html") { - verify(core).createPost(currentSone, absent(), "post KSK@foo text") - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/CreateSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/CreateSonePageTest.kt deleted file mode 100644 index 80239e9..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/CreateSonePageTest.kt +++ /dev/null @@ -1,148 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Profile -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.freenet.wot.OwnIdentity -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.verify - -/** - * Unit test for [CreateSonePage]. - */ -class CreateSonePageTest: WebPageTest() { - - private val page = CreateSonePage(template, webInterface) - override fun getPage() = page - - private val localSones_ = listOf( - createSone("local-sone1"), - createSone("local-sone2"), - createSone("local-sone3") - ) - - private fun createSone(id: String) = mock().apply { - whenever(this.id).thenReturn(id) - whenever(profile).thenReturn(Profile(this)) - } - - private val ownIdentities_ = listOf( - createOwnIdentity("own-id-1", "Sone"), - createOwnIdentity("own-id-2", "Test", "Foo"), - createOwnIdentity("own-id-3"), - createOwnIdentity("own-id-4", "Sone") - ) - - private fun createOwnIdentity(id: String, vararg contexts: String) = mock().apply { - whenever(this.id).thenReturn(id) - whenever(this.nickname).thenReturn(id) - whenever(this.contexts).thenReturn(contexts.toSet()) - whenever(this.hasContext(anyString())).thenAnswer { invocation -> invocation.getArgument(0) in contexts } - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("createSone.html")) - } - - @Test - fun `page does not require login`() { - assertThat(page.requiresLogin(), equalTo(false)) - } - - private fun addExistingSones() { - listOf(2, 0, 1).map { localSones_[it] }.forEach { addLocalSone(it.id, it) } - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `get request stores sorted list of local sones in template context`() { - addExistingSones() - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["sones"] as Collection, contains(localSones_[0], localSones_[1], localSones_[2])) - } - - private fun addExistingOwnIdentities() { - listOf(2, 0, 3, 1).map { ownIdentities_[it] }.forEach { addOwnIdentity(it) } - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `get request stores sorted sones without sone context in the template context`() { - addExistingOwnIdentities() - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["identitiesWithoutSone"] as Collection, contains(ownIdentities_[1], ownIdentities_[2])) - } - - @Test - fun `sone is created and logged in`() { - addExistingOwnIdentities() - request("", POST) - addHttpRequestParameter("identity", "own-id-3") - val newSone = mock() - whenever(core.createSone(ownIdentities_[2])).thenReturn(newSone) - verifyRedirect("index.html") { - verify(webInterface).setCurrentSone(toadletContext, newSone) - } - } - - @Test - fun `on invalid identity id a flag is set in the template context`() { - request("", POST) - addHttpRequestParameter("identity", "own-id-3") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["errorNoIdentity"], equalTo(true)) - } - - @Test - fun `if sone is not created user is still redirected to index`() { - addExistingOwnIdentities() - request("", POST) - addHttpRequestParameter("identity", "own-id-3") - whenever(core.createSone(ownIdentities_[2])).thenReturn(null) - verifyRedirect("index.html") { - verify(core).createSone(ownIdentities_[2]) - verify(webInterface).setCurrentSone(toadletContext, null) - } - } - - @Test - fun `create sone is not shown in menu if full access is required but client doesn’t have full access`() { - core.preferences.isRequireFullAccess = true - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `create sone is shown in menu if no sone is logged in`() { - unsetCurrentSone() - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - - @Test - fun `create sone is shown in menu if a single sone exists`() { - addLocalSone("local-sone", localSones_[0]) - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - - @Test - fun `create sone is not shown in menu if more than one sone exists`() { - addLocalSone("local-sone1", localSones_[0]) - addLocalSone("local-sone2", localSones_[1]) - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `create sone is shown in menu if no sone is logged in and client has full access`() { - core.preferences.isRequireFullAccess = true - whenever(toadletContext.isAllowedFullAccess).thenReturn(true) - unsetCurrentSone() - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/DeleteAlbumPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/DeleteAlbumPageTest.kt deleted file mode 100644 index 9276be7..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/DeleteAlbumPageTest.kt +++ /dev/null @@ -1,114 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.verify - -/** - * Unit test for [DeleteAlbumPage]. - */ -class DeleteAlbumPageTest: WebPageTest() { - - private val page = DeleteAlbumPage(template, webInterface) - - private val sone = mock() - private val album = mock() - private val parentAlbum = mock() - - override fun getPage() = page - - @Before - fun setupAlbums() { - whenever(sone.id).thenReturn("sone-id") - whenever(sone.isLocal).thenReturn(true) - whenever(parentAlbum.id).thenReturn("parent-id") - whenever(parentAlbum.isRoot).thenReturn(true) - whenever(album.id).thenReturn("album-id") - whenever(album.sone).thenReturn(sone) - whenever(album.parent).thenReturn(parentAlbum) - whenever(sone.rootAlbum).thenReturn(parentAlbum) - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("deleteAlbum.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `get request with invalid album ID results in redirect to invalid page`() { - request("", GET) - whenever(core.getAlbum(anyString())).thenReturn(null) - verifyRedirect("invalid.html") - } - - @Test - fun `get request with valid album ID sets album in template context`() { - request("", GET) - val album = mock() - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["album"], equalTo(album)) - } - - @Test - fun `post request redirects to invalid page if album is invalid`() { - request("", POST) - verifyRedirect("invalid.html") - } - - @Test - fun `post request redirects to no permissions page if album is not local`() { - request("", POST) - whenever(sone.isLocal).thenReturn(false) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - verifyRedirect("noPermission.html") - } - - @Test - fun `post request with abort delete parameter set redirects to album browser`() { - request("", POST) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - addHttpRequestParameter("abortDelete", "true") - verifyRedirect("imageBrowser.html?album=album-id") - } - - @Test - fun `album is deleted and page redirects to sone if parent album is root album`() { - request("", POST) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - verifyRedirect("imageBrowser.html?sone=sone-id") { - verify(core).deleteAlbum(album) - } - } - - @Test - fun `album is deleted and page redirects to album if parent album is not root album`() { - request("", POST) - whenever(parentAlbum.isRoot).thenReturn(false) - whenever(sone.rootAlbum).thenReturn(mock()) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - verifyRedirect("imageBrowser.html?album=parent-id") { - verify(core).deleteAlbum(album) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/DeleteImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/DeleteImagePageTest.kt deleted file mode 100644 index 3f617cd..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/DeleteImagePageTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Image -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [DeleteImagePage]. - */ -class DeleteImagePageTest: WebPageTest() { - - private val page = DeleteImagePage(template, webInterface) - private val image = mock() - private val sone = mock() - - override fun getPage() = page - - @Before - fun setupImage() { - val album = mock() - whenever(album.id).thenReturn("album-id") - whenever(image.id).thenReturn("image-id") - whenever(image.sone).thenReturn(sone) - whenever(image.album).thenReturn(album) - whenever(sone.isLocal).thenReturn(true) - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("deleteImage.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `get request with invalid image redirects to invalid page`() { - request("", GET) - verifyRedirect("invalid.html") - } - - @Test - fun `get request with image from non-local sone redirects to no permissions page`() { - request("", GET) - whenever(sone.isLocal).thenReturn(false) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - verifyRedirect("noPermission.html") - } - - @Test - fun `get request with image from local sone sets image in template context`() { - request("", GET) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["image"], equalTo(image)) - } - - @Test - fun `post request with abort delete flag set redirects to image browser`() { - request("", POST) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - addHttpRequestParameter("abortDelete", "true") - verifyRedirect("imageBrowser.html?image=image-id") - } - - @Test - fun `post request deletes image and redirects to image browser`() { - request("", POST) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - verifyRedirect("imageBrowser.html?album=album-id") { - verify(webInterface.core).deleteImage(image) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/DeletePostPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/DeletePostPageTest.kt deleted file mode 100644 index ab7851a..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/DeletePostPageTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [DeletePostPage]. - */ -class DeletePostPageTest : WebPageTest() { - - private val page = DeletePostPage(template, webInterface) - - private val post = mock() - private val sone = mock() - - override fun getPage() = page - - @Before - fun setupPost() { - whenever(post.sone).thenReturn(sone) - whenever(sone.isLocal).thenReturn(true) - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("deletePost.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `get request with invalid post redirects to no permission page`() { - request("", GET) - verifyRedirect("noPermission.html") - } - - @Test - fun `get request with valid post sets post and return page in template context`() { - request("", GET) - addPost("post-id", post) - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("returnPage", "return.html") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["post"], equalTo(post)) - assertThat(templateContext["returnPage"], equalTo("return.html")) - } - - @Test - fun `post request with invalid post redirects to no permission page`() { - request("", POST) - verifyRedirect("noPermission.html") - } - - @Test - fun `post request with post from non-local sone redirects to no permission page`() { - request("", POST) - whenever(sone.isLocal).thenReturn(false) - addPost("post-id", post) - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("noPermission.html") - } - - @Test - fun `post request with confirmation deletes post and redirects to return page`() { - request("", POST) - addPost("post-id", post) - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("confirmDelete", "true") - verifyRedirect("return.html") { - verify(core).deletePost(post) - } - } - - @Test - fun `post request with abort delete does not delete post and redirects to return page`() { - request("", POST) - addPost("post-id", post) - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("abortDelete", "true") - verifyRedirect("return.html") { - verify(core, never()).deletePost(post) - } - } - - @Test - fun `post request without delete or abort sets post in template context`() { - request("", POST) - addPost("post-id", post) - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("returnPage", "return.html") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["post"], equalTo(post)) - assertThat(templateContext["returnPage"], equalTo("return.html")) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/DeleteProfileFieldPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/DeleteProfileFieldPageTest.kt deleted file mode 100644 index 4c5a07a..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/DeleteProfileFieldPageTest.kt +++ /dev/null @@ -1,85 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Profile -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.nullValue -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.any -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [DeleteProfileFieldPage]. - */ -class DeleteProfileFieldPageTest: WebPageTest() { - - private val page = DeleteProfileFieldPage(template, webInterface) - - private val profile = Profile(currentSone) - private val field = profile.addField("name") - - override fun getPage() = page - - @Before - fun setupProfile() { - whenever(currentSone.profile).thenReturn(profile) - field.value = "value" - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("deleteProfileField.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `get request with invalid field name redirects to invalid page`() { - request("", GET) - verifyRedirect("invalid.html") - } - - @Test - fun `post request with invalid field name redirects to invalid page`() { - request("", POST) - addHttpRequestParameter("field", "wrong-id") - verifyRedirect("invalid.html") - } - - @Test - fun `get request with valid field name sets field in template context`() { - request("", GET) - addHttpRequestParameter("field", field.id) - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["field"], equalTo(field)) - } - - @Test - fun `post request without confirm redirects to edit profile page`() { - request("", POST) - addHttpRequestParameter("field", field.id) - verifyRedirect("editProfile.html#profile-fields") { - verify(currentSone, never()).profile = any() - } - } - - @Test - fun `post request with confirm removes field and redirects to edit profile page`() { - request("", POST) - addHttpRequestParameter("field", field.id) - addHttpRequestParameter("confirm", "true") - verifyRedirect("editProfile.html#profile-fields") { - assertThat(profile.getFieldById(field.id), nullValue()) - verify(currentSone).profile = profile - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/DeleteReplyPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/DeleteReplyPageTest.kt deleted file mode 100644 index b7b92ae..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/DeleteReplyPageTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.PostReply -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [DeleteReplyPage]. - */ -class DeleteReplyPageTest : WebPageTest() { - - private val page = DeleteReplyPage(template, webInterface) - - private val sone = mock() - private val reply = mock() - - override fun getPage() = page - - @Before - fun setupReply() { - whenever(sone.isLocal).thenReturn(true) - whenever(reply.sone).thenReturn(sone) - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("deleteReply.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `get request sets reply ID and return page in template context`() { - request("", GET) - addHttpRequestParameter("reply", "reply-id") - addHttpRequestParameter("returnPage", "return.html") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["reply"], equalTo("reply-id")) - assertThat(templateContext["returnPage"], equalTo("return.html")) - } - - @Test - fun `post request without any action sets reply ID and return page in template context`() { - request("", POST) - addPostReply("reply-id", reply) - addHttpRequestParameter("reply", "reply-id") - addHttpRequestParameter("returnPage", "return.html") - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["reply"], equalTo("reply-id")) - assertThat(templateContext["returnPage"], equalTo("return.html")) - } - - @Test - fun `trying to delete a reply with an invalid ID results in no permission page`() { - request("", POST) - verifyRedirect("noPermission.html") - } - - @Test - fun `trying to delete a reply from a non-local sone results in no permission page`() { - request("", POST) - addHttpRequestParameter("reply", "reply-id") - whenever(sone.isLocal).thenReturn(false) - addPostReply("reply-id", reply) - verifyRedirect("noPermission.html") - } - - @Test - fun `confirming deletion of reply deletes the reply and redirects to return page`() { - request("", POST) - addPostReply("reply-id", reply) - addHttpRequestParameter("reply", "reply-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("confirmDelete", "true") - verifyRedirect("return.html") { - verify(core).deleteReply(reply) - } - } - - @Test - fun `aborting deletion of reply redirects to return page`() { - request("", POST) - addPostReply("reply-id", reply) - addHttpRequestParameter("reply", "reply-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("abortDelete", "true") - verifyRedirect("return.html") { - verify(core, never()).deleteReply(reply) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/DeleteSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/DeleteSonePageTest.kt deleted file mode 100644 index b51ebed..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/DeleteSonePageTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.any -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [DeleteSonePage]. - */ -class DeleteSonePageTest : WebPageTest() { - - private val page = DeleteSonePage(template, webInterface) - - override fun getPage() = page - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("deleteSone.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `page returns correct title`() { - whenever(l10n.getString("Page.DeleteSone.Title")).thenReturn("delete sone page") - assertThat(page.getPageTitle(freenetRequest), equalTo("delete sone page")) - } - - @Test - fun `get request does not redirect`() { - request("", GET) - page.processTemplate(freenetRequest, templateContext) - } - - @Test - fun `post request without delete confirmation redirects to index`() { - request("", POST) - verifyRedirect("index.html") { - verify(core, never()).deleteSone(any()) - } - } - - @Test - fun `post request with delete confirmation deletes sone and redirects to index`() { - request("", POST) - addHttpRequestParameter("deleteSone", "true") - verifyRedirect("index.html") { - verify(core).deleteSone(currentSone) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/DismissNotificationPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/DismissNotificationPageTest.kt deleted file mode 100644 index 3c19f5c..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/DismissNotificationPageTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.notify.Notification -import net.pterodactylus.util.web.Method.GET -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [DismissNotificationPage]. - */ -class DismissNotificationPageTest: WebPageTest() { - - private val page = DismissNotificationPage(template, webInterface) - private val notification = mock() - - override fun getPage() = page - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("dismissNotification.html")) - } - - @Test - fun `page does not require login`() { - assertThat(page.requiresLogin(), equalTo(false)) - } - - @Test - fun `page returns correct title`() { - whenever(l10n.getString("Page.DismissNotification.Title")).thenReturn("dismiss notification page") - assertThat(page.getPageTitle(freenetRequest), equalTo("dismiss notification page")) - } - - @Test - fun `get request with invalid notification ID redirects to return page`() { - request("", GET) - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") - } - - @Test - fun `get request with non-dismissible notification never dismisses the notification but redirects to return page`() { - request("", GET) - addNotification("notification-id", notification) - addHttpRequestParameter("notification", "notification-id") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(notification, never()).dismiss() - } - } - - @Test - fun `get request with dismissible notification dismisses the notification and redirects to return page`() { - request("", GET) - whenever(notification.isDismissable).thenReturn(true) - addNotification("notification-id", notification) - addHttpRequestParameter("notification", "notification-id") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(notification).dismiss() - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/DistrustPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/DistrustPageTest.kt deleted file mode 100644 index 8346a7f..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/DistrustPageTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [DistrustPage]. - */ -class DistrustPageTest: WebPageTest() { - - private val page = DistrustPage(template, webInterface) - - override fun getPage() = page - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("distrust.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `page returns correct title`() { - whenever(l10n.getString("Page.Distrust.Title")).thenReturn("distrust page title") - assertThat(page.getPageTitle(freenetRequest), equalTo("distrust page title")) - } - - @Test - fun `get request does not redirect`() { - request("", GET) - page.processTemplate(freenetRequest, templateContext) - } - - @Test - fun `post request with invalid sone redirects to return page`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") - } - - @Test - fun `post request with valid sone distrusts sone and redirects to return page`() { - request("", POST) - val remoteSone = mock() - addSone("remote-sone-id", remoteSone) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "remote-sone-id") - verifyRedirect("return.html") { - verify(core).distrustSone(currentSone, remoteSone) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/EditAlbumPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/EditAlbumPageTest.kt deleted file mode 100644 index ed96a80..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/EditAlbumPageTest.kt +++ /dev/null @@ -1,128 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.mockBuilder -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [EditAlbumPage]. - */ -class EditAlbumPageTest: WebPageTest() { - - private val page = EditAlbumPage(template, webInterface) - - private val album = mock() - private val parentAlbum = mock() - private val modifier = mockBuilder() - private val sone = mock() - - override fun getPage() = page - - @Before - fun setup() { - whenever(album.id).thenReturn("album-id") - whenever(album.sone).thenReturn(sone) - whenever(album.parent).thenReturn(parentAlbum) - whenever(album.modify()).thenReturn(modifier) - whenever(modifier.update()).thenReturn(album) - whenever(parentAlbum.id).thenReturn("parent-id") - whenever(sone.isLocal).thenReturn(true) - addHttpRequestHeader("Host", "www.te.st") - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("editAlbum.html")) - } - - @Test - fun `page requires login`() { - assertThat(page.requiresLogin(), equalTo(true)) - } - - @Test - fun `page returns correct title`() { - whenever(l10n.getString("Page.EditAlbum.Title")).thenReturn("edit album page") - assertThat(page.getPageTitle(freenetRequest), equalTo("edit album page")) - } - - @Test - fun `get request does not redirect`() { - request("", GET) - page.processTemplate(freenetRequest, templateContext) - } - - @Test - fun `post request with invalid album redirects to invalid page`() { - request("", POST) - verifyRedirect("invalid.html") - } - - @Test - fun `post request with album of non-local sone redirects to no permissions page`() { - request("", POST) - whenever(sone.isLocal).thenReturn(false) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - verifyRedirect("noPermission.html") - } - - @Test - fun `post request with move left requested moves album to the left and redirects to album browser`() { - request("", POST) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - addHttpRequestParameter("moveLeft", "true") - verifyRedirect("imageBrowser.html?album=parent-id") { - verify(parentAlbum).moveAlbumUp(album) - verify(core).touchConfiguration() - } - } - - @Test - fun `post request with move right requested moves album to the left and redirects to album browser`() { - request("", POST) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - addHttpRequestParameter("moveRight", "true") - verifyRedirect("imageBrowser.html?album=parent-id") { - verify(parentAlbum).moveAlbumDown(album) - verify(core).touchConfiguration() - } - } - - @Test - fun `post request with empty album title redirects to empty album title page`() { - request("", POST) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - whenever(modifier.setTitle("")).thenThrow(AlbumTitleMustNotBeEmpty()) - verifyRedirect("emptyAlbumTitle.html") - } - - @Test - fun `post request with non-empty album title and description redirects to album browser`() { - request("", POST) - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - addHttpRequestParameter("title", "title") - addHttpRequestParameter("description", "description") - verifyRedirect("imageBrowser.html?album=album-id") { - verify(modifier).setTitle("title") - verify(modifier).setDescription("description") - verify(modifier).update() - verify(core).touchConfiguration() - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/EditImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/EditImagePageTest.kt deleted file mode 100644 index 04ce570..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/EditImagePageTest.kt +++ /dev/null @@ -1,132 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Image -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.mockBuilder -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [EditImagePage]. - */ -class EditImagePageTest : WebPageTest() { - - private val page = EditImagePage(template, webInterface) - - private val image = mock() - private val modifier = mockBuilder() - private val sone = mock() - private val album = mock() - - override fun getPage() = page - - @Before - fun setupImage() { - whenever(sone.isLocal).thenReturn(true) - whenever(album.id).thenReturn("album-id") - whenever(modifier.update()).thenReturn(image) - whenever(image.sone).thenReturn(sone) - whenever(image.album).thenReturn(album) - whenever(image.modify()).thenReturn(modifier) - } - - @Test - fun `get request does not redirect`() { - request("", GET) - page.handleRequest(freenetRequest, templateContext) - } - - @Test - fun `post request with invalid image redirects to invalid page`() { - request("", POST) - verifyRedirect("invalid.html") - } - - @Test - fun `post request with valid image from non-local sone redirects to no permission page`() { - request("", POST) - whenever(sone.isLocal).thenReturn(false) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - verifyRedirect("noPermission.html") - } - - @Test - fun `post request with valid image and move left requested moves image left and redirects to return page`() { - request("", POST) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("moveLeft", "true") - verifyRedirect("return.html") { - verify(album).moveImageUp(image) - verify(core).touchConfiguration() - } - } - - @Test - fun `post request with valid image and move right requested moves image right and redirects to return page`() { - request("", POST) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("moveRight", "true") - verifyRedirect("return.html") { - verify(album).moveImageDown(image) - verify(core).touchConfiguration() - } - } - - @Test - fun `post request with valid image but only whitespace in the title redirects to empty image title page`() { - request("", POST) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("title", " ") - verifyRedirect("emptyImageTitle.html") { - verify(core, never()).touchConfiguration() - } - } - - @Test - fun `post request with valid image title and description modifies image and redirects to reutrn page`() { - request("", POST) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("title", "Title") - addHttpRequestParameter("description", "Description") - verifyRedirect("return.html") { - verify(modifier).setTitle("Title") - verify(modifier).setDescription("Description") - verify(modifier).update() - verify(core).touchConfiguration() - } - } - - @Test - fun `post request with image title and description modifies image with filtered description and redirects to reutrn page`() { - request("", POST) - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("title", "Title") - addHttpRequestHeader("Host", "www.te.st") - addHttpRequestParameter("description", "Get http://www.te.st/KSK@GPL.txt") - verifyRedirect("return.html") { - verify(modifier).setTitle("Title") - verify(modifier).setDescription("Get KSK@GPL.txt") - verify(modifier).update() - verify(core).touchConfiguration() - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/EditProfileFieldPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/EditProfileFieldPageTest.kt deleted file mode 100644 index 01d78ca..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/EditProfileFieldPageTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Profile -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [EditProfileFieldPage]. - */ -class EditProfileFieldPageTest : WebPageTest() { - - private val page = EditProfileFieldPage(template, webInterface) - - private val profile = Profile(currentSone) - private val field = profile.addField("Name") - - override fun getPage() = page - - @Before - fun setupProfile() { - whenever(currentSone.profile).thenReturn(profile) - } - - @Test - fun `get request with invalid field redirects to invalid page`() { - request("", GET) - verifyRedirect("invalid.html") - } - - @Test - fun `get request with valid field stores field in template context`() { - request("", GET) - addHttpRequestParameter("field", field.id) - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["field"], equalTo(field)) - } - - @Test - fun `post request with cancel set redirects to profile edit page`() { - request("", POST) - addHttpRequestParameter("field", field.id) - addHttpRequestParameter("cancel", "true") - verifyRedirect("editProfile.html#profile-fields") - } - - @Test - fun `post request with new name renames field and redirects to profile edit page`() { - request("", POST) - addHttpRequestParameter("field", field.id) - addHttpRequestParameter("name", "New Name") - verifyRedirect("editProfile.html#profile-fields") { - assertThat(field.name, equalTo("New Name")) - verify(currentSone).profile = profile - } - } - - @Test - fun `post request with same name does not modify field and redirects to profile edit page`() { - request("", POST) - addHttpRequestParameter("field", field.id) - addHttpRequestParameter("name", "Name") - verifyRedirect("editProfile.html#profile-fields") { - assertThat(field.name, equalTo("Name")) - verify(currentSone, never()).profile = profile - } - } - - @Test - fun `post request with same name as different field sets error condition in template`() { - request("", POST) - profile.addField("New Name") - addHttpRequestParameter("field", field.id) - addHttpRequestParameter("name", "New Name") - page.handleRequest(freenetRequest, templateContext) - assertThat(field.name, equalTo("Name")) - verify(currentSone, never()).profile = profile - assertThat(templateContext["duplicateFieldName"], equalTo(true)) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/EditProfilePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/EditProfilePageTest.kt deleted file mode 100644 index 0fb6cca..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/EditProfilePageTest.kt +++ /dev/null @@ -1,210 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Image -import net.pterodactylus.sone.data.Profile -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.notNullValue -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [EditProfilePage]. - */ -class EditProfilePageTest : WebPageTest() { - - private val page = EditProfilePage(template, webInterface) - - private val profile = Profile(currentSone) - private val firstField = profile.addField("First Field") - private val secondField = profile.addField("Second Field") - - override fun getPage() = page - - @Before - fun setupProfile() { - val avatar = mock() - whenever(avatar.id).thenReturn("image-id") - whenever(avatar.sone).thenReturn(currentSone) - profile.firstName = "First" - profile.middleName = "Middle" - profile.lastName = "Last" - profile.birthDay = 31 - profile.birthMonth = 12 - profile.birthYear = 1999 - profile.setAvatar(avatar) - whenever(currentSone.profile).thenReturn(profile) - } - - @Test - fun `get request stores fields of current sone’s profile in template context`() { - request("", GET) - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["firstName"], equalTo("First")) - assertThat(templateContext["middleName"], equalTo("Middle")) - assertThat(templateContext["lastName"], equalTo("Last")) - assertThat(templateContext["birthDay"], equalTo(31)) - assertThat(templateContext["birthMonth"], equalTo(12)) - assertThat(templateContext["birthYear"], equalTo(1999)) - assertThat(templateContext["avatarId"], equalTo("image-id")) - assertThat(templateContext["fields"], equalTo(listOf(firstField, secondField))) - } - - @Test - fun `post request without any command stores fields of current sone’s profile in template context`() { - request("", POST) - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["firstName"], equalTo("First")) - assertThat(templateContext["middleName"], equalTo("Middle")) - assertThat(templateContext["lastName"], equalTo("Last")) - assertThat(templateContext["birthDay"], equalTo(31)) - assertThat(templateContext["birthMonth"], equalTo(12)) - assertThat(templateContext["birthYear"], equalTo(1999)) - assertThat(templateContext["avatarId"], equalTo("image-id")) - assertThat(templateContext["fields"], equalTo(listOf(firstField, secondField))) - } - - private fun verifySingleFieldCanBeChanged(fieldName: String, newValue: T, expectedValue: T = newValue, fieldAccessor: () -> T) { - request("", POST) - addHttpRequestParameter("save-profile", "true") - addHttpRequestParameter(fieldName, newValue.toString()) - verifyRedirect("editProfile.html") { - verify(core).touchConfiguration() - assertThat(fieldAccessor(), equalTo(expectedValue)) - } - } - - @Test - fun `post request with new first name and save profile saves the profile and redirects back to profile edit page`() { - verifySingleFieldCanBeChanged("first-name", "New First") { profile.firstName } - } - - @Test - fun `post request with new middle name and save profile saves the profile and redirects back to profile edit page`() { - verifySingleFieldCanBeChanged("middle-name", "New Middle") { profile.middleName } - } - - @Test - fun `post request with new last name and save profile saves the profile and redirects back to profile edit page`() { - verifySingleFieldCanBeChanged("last-name", "New Last") { profile.lastName } - } - - @Test - fun `post request with new birth day and save profile saves the profile and redirects back to profile edit page`() { - verifySingleFieldCanBeChanged("birth-day", 1) { profile.birthDay } - } - - @Test - fun `post request with new birth month and save profile saves the profile and redirects back to profile edit page`() { - verifySingleFieldCanBeChanged("birth-month", 1) { profile.birthMonth } - } - - @Test - fun `post request with new birth year and save profile saves the profile and redirects back to profile edit page`() { - verifySingleFieldCanBeChanged("birth-year", 1) { profile.birthYear } - } - - @Test - fun `post request with new avatar ID and save profile saves the profile and redirects back to profile edit page`() { - val newAvatar = mock() - whenever(newAvatar.sone).thenReturn(currentSone) - whenever(newAvatar.id).thenReturn("avatar-id") - addImage("avatar-id", newAvatar) - verifySingleFieldCanBeChanged("avatarId", "avatar-id") { profile.avatar } - } - - @Test - fun `post request with field value saves profile and redirects back to profile edit page`() { - val field = profile.addField("name") - field.value = "old" - verifySingleFieldCanBeChanged("field-${field.id}", "new") { profile.getFieldByName("name")!!.value } - } - - @Test - fun `post request with field value saves filtered value to profile and redirects back to profile edit page`() { - val field = profile.addField("name") - field.value = "old" - addHttpRequestHeader("Host", "www.te.st") - verifySingleFieldCanBeChanged("field-${field.id}", "http://www.te.st/KSK@GPL.txt", "KSK@GPL.txt") { profile.getFieldByName("name")!!.value } - } - - @Test - fun `adding a field with a duplicate name sets error in template context`() { - request("", POST) - profile.addField("new-field") - addHttpRequestParameter("add-field", "true") - addHttpRequestParameter("field-name", "new-field") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["fieldName"], equalTo("new-field")) - assertThat(templateContext["duplicateFieldName"], equalTo(true)) - verify(core, never()).touchConfiguration() - } - - @Test - fun `adding a field with a new name sets adds field to profile and redirects to profile edit page`() { - request("", POST) - addHttpRequestParameter("add-field", "true") - addHttpRequestParameter("field-name", "new-field") - verifyRedirect("editProfile.html#profile-fields") { - assertThat(profile.getFieldByName("new-field"), notNullValue()) - verify(currentSone).profile = profile - verify(core).touchConfiguration() - } - } - - @Test - fun `deleting a field redirects to delete field page`() { - request("", POST) - addHttpRequestParameter("delete-field-${firstField.id}", "true") - verifyRedirect("deleteProfileField.html?field=${firstField.id}") - } - - @Test - fun `moving a field up moves the field up and redirects to the edit profile page`() { - request("", POST) - addHttpRequestParameter("move-up-field-${secondField.id}", "true") - verifyRedirect("editProfile.html#profile-fields") { - assertThat(profile.fields, contains(secondField, firstField)) - verify(currentSone).profile = profile - } - } - - @Test - fun `moving an invalid field up redirects to the invalid page`() { - request("", POST) - addHttpRequestParameter("move-up-field-foo", "true") - verifyRedirect("invalid.html") - } - - @Test - fun `moving a field down moves the field down and redirects to the edit profile page`() { - request("", POST) - addHttpRequestParameter("move-down-field-${firstField.id}", "true") - verifyRedirect("editProfile.html#profile-fields") { - assertThat(profile.fields, contains(secondField, firstField)) - verify(currentSone).profile = profile - } - } - - @Test - fun `moving an invalid field down redirects to the invalid page`() { - request("", POST) - addHttpRequestParameter("move-down-field-foo", "true") - verifyRedirect("invalid.html") - } - - @Test - fun `editing a field redirects to the edit profile page`() { - request("", POST) - addHttpRequestParameter("edit-field-${firstField.id}", "true") - verifyRedirect("editProfileField.html?field=${firstField.id}") - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/FollowSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/FollowSonePageTest.kt deleted file mode 100644 index 1f6fc26..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/FollowSonePageTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.junit.Test -import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [FollowSonePage]. - */ -class FollowSonePageTest : WebPageTest() { - - private val page = FollowSonePage(template, webInterface) - - override fun getPage() = page - - @Test - fun `get request does not redirect`() { - request("", GET) - page.handleRequest(freenetRequest, templateContext) - } - - @Test - fun `a single sone can be followed`() { - request("", POST) - val sone = mock() - addSone("sone-id", sone) - addHttpRequestParameter("sone", "sone-id") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(core).followSone(currentSone, "sone-id") - verify(core).markSoneKnown(sone) - } - } - - @Test - fun `multiple sones can be followed`() { - request("", POST) - val firstSone = mock() - addSone("sone-id1", firstSone) - val secondSone = mock() - addSone("sone-id2", secondSone) - addHttpRequestParameter("sone", "sone-id1,sone-id2") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(core).followSone(currentSone, "sone-id1") - verify(core).followSone(currentSone, "sone-id2") - verify(core).markSoneKnown(firstSone) - verify(core).markSoneKnown(secondSone) - } - } - - @Test - fun `a non-existing sone is not followed`() { - request("", POST) - addHttpRequestParameter("sone", "sone-id") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(core, never()).followSone(ArgumentMatchers.eq(currentSone), anyString()) - verify(core, never()).markSoneKnown(any()) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/GetImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/GetImagePageTest.kt deleted file mode 100644 index 57f887a..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/GetImagePageTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.TemporaryImage -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test - -/** - * Unit test for [GetImagePage]. - */ -class GetImagePageTest : WebPageTest() { - - private val page = GetImagePage(webInterface) - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("getImage.html")) - } - - @Test - fun `page is not a prefix page`() { - assertThat(page.isPrefixPage, equalTo(false)) - } - - @Test - fun `page is not link-excepted`() { - assertThat(page.isLinkExcepted(null), equalTo(false)) - } - - @Test - fun `invalid image returns 404 response`() { - page.handleRequest(freenetRequest, response) - assertThat(response.statusCode, equalTo(404)) - assertThat(responseBytes, equalTo(ByteArray(0))) - } - - @Test - fun `valid image returns response with correct data`() { - val image = TemporaryImage("temp-id").apply { - mimeType = "image/test" - imageData = ByteArray(5, Int::toByte) - } - addHttpRequestParameter("image", "temp-id") - addTemporaryImage("temp-id", image) - page.handleRequest(freenetRequest, response) - assertThat(response.statusCode, equalTo(200)) - assertThat(response.contentType, equalTo("image/test")) - assertThat(responseBytes, equalTo(ByteArray(5, Int::toByte))) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/ImageBrowserPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/ImageBrowserPageTest.kt deleted file mode 100644 index 426eaf4..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/ImageBrowserPageTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Image -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.hamcrest.Matchers.equalTo -import org.junit.Test - -/** - * Unit test for [ImageBrowserPage]. - */ -class ImageBrowserPageTest : WebPageTest() { - - private val page = ImageBrowserPage(template, webInterface) - - @Test - fun `get request with album sets album and page in template context`() { - request("", GET) - val album = mock() - addAlbum("album-id", album) - addHttpRequestParameter("album", "album-id") - addHttpRequestParameter("page", "5") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["albumRequested"], equalTo(true)) - assertThat(templateContext["album"], equalTo(album)) - assertThat(templateContext["page"], equalTo("5")) - } - - @Test - fun `get request with image sets image in template context`() { - request("", GET) - val image = mock() - addImage("image-id", image) - addHttpRequestParameter("image", "image-id") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["imageRequested"], equalTo(true)) - assertThat(templateContext["image"], equalTo(image)) - } - - @Test - fun `get request with sone sets sone in template context`() { - request("", GET) - val sone = mock() - addSone("sone-id", sone) - addHttpRequestParameter("sone", "sone-id") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["soneRequested"], equalTo(true)) - assertThat(templateContext["sone"], equalTo(sone)) - } - - @Test - fun `get request with mode of gallery sets albums and page in template context`() { - request("", GET) - val firstSone = createSone("first album", "second album") - addSone("sone1", firstSone) - val secondSone = createSone("third album", "fourth album") - addSone("sone2", secondSone) - addHttpRequestParameter("mode", "gallery") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["galleryRequested"], equalTo(true)) - @Suppress("UNCHECKED_CAST") - assertThat(templateContext["albums"] as Iterable, contains( - firstSone.rootAlbum.albums[0], - secondSone.rootAlbum.albums[1], - firstSone.rootAlbum.albums[1], - secondSone.rootAlbum.albums[0] - )) - } - - private fun createSone(firstAlbumTitle: String, secondAlbumTitle: String): Sone { - return mock().apply { - val rootAlbum = mock() - val firstAlbum = mock() - val firstImage = mock().run { whenever(isInserted).thenReturn(true); this } - whenever(firstAlbum.images).thenReturn(listOf(firstImage)) - val secondAlbum = mock() - val secondImage = mock().run { whenever(isInserted).thenReturn(true); this } - whenever(secondAlbum.images).thenReturn(listOf(secondImage)) - whenever(firstAlbum.title).thenReturn(firstAlbumTitle) - whenever(secondAlbum.title).thenReturn(secondAlbumTitle) - whenever(rootAlbum.albums).thenReturn(listOf(firstAlbum, secondAlbum)) - whenever(this.rootAlbum).thenReturn(rootAlbum) - } - } - - @Test - fun `requesting nothing will show the albums of the current sone`() { - request("", GET) - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["soneRequested"], equalTo(true)) - assertThat(templateContext["sone"], equalTo(currentSone)) - } - - @Test - fun `page is link-excepted`() { - assertThat(page.isLinkExcepted(null), equalTo(true)) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/IndexPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/IndexPageTest.kt deleted file mode 100644 index 414c0f2..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/IndexPageTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -package net.pterodactylus.sone.web - -import com.google.common.base.Optional.fromNullable -import com.google.common.base.Predicate -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.notify.PostVisibilityFilter -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.junit.Before -import org.junit.Test -import org.mockito.ArgumentMatchers - -/** - * Unit test for [IndexPage]. - */ -class IndexPageTest : WebPageTest() { - - private val postVisibilityFilter = mock() - private val page = IndexPage(template, webInterface, postVisibilityFilter) - - @Before - fun setupPostVisibilityFilter() { - whenever(postVisibilityFilter.isVisible(ArgumentMatchers.eq(currentSone))).thenReturn(object : Predicate { - override fun apply(input: Post?) = true - }) - } - - private fun createPost(time: Long, directed: Boolean = false) = mock().apply { - whenever(this.time).thenReturn(time) - whenever(recipient).thenReturn(fromNullable(if (directed) currentSone else null)) - } - - @Test - fun `index page shows all posts of current sone`() { - val posts = listOf(createPost(3000), createPost(2000), createPost(1000)) - whenever(currentSone.posts).thenReturn(posts) - request("", GET) - page.handleRequest(freenetRequest, templateContext) - @Suppress("UNCHECKED_CAST") - assertThat(templateContext["posts"] as Iterable, contains(*posts.toTypedArray())) - } - - @Test - fun `index page shows posts directed at current sone from non-followed sones`() { - val posts = listOf(createPost(3000), createPost(2000), createPost(1000)) - whenever(currentSone.posts).thenReturn(posts) - val notFollowedSone = mock() - val notFollowedPosts = listOf(createPost(2500, true), createPost(1500)) - whenever(notFollowedSone.posts).thenReturn(notFollowedPosts) - addSone("notfollowed1", notFollowedSone) - request("", GET) - page.handleRequest(freenetRequest, templateContext) - @Suppress("UNCHECKED_CAST") - assertThat(templateContext["posts"] as Iterable, contains( - posts[0], notFollowedPosts[0], posts[1], posts[2] - )) - } - - @Test - fun `index page does not show duplicate posts`() { - val posts = listOf(createPost(3000), createPost(2000), createPost(1000)) - whenever(currentSone.posts).thenReturn(posts) - val followedSone = mock() - val followedPosts = listOf(createPost(2500, true), createPost(1500)) - whenever(followedSone.posts).thenReturn(followedPosts) - whenever(currentSone.friends).thenReturn(listOf("followed1", "followed2")) - addSone("followed1", followedSone) - request("", GET) - page.handleRequest(freenetRequest, templateContext) - @Suppress("UNCHECKED_CAST") - assertThat(templateContext["posts"] as Iterable, contains( - posts[0], followedPosts[0], posts[1], followedPosts[1], posts[2] - )) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/KnownSonesPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/KnownSonesPageTest.kt deleted file mode 100644 index f0ee9db..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/KnownSonesPageTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Image -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.PostReply -import net.pterodactylus.sone.data.Profile -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.freenet.wot.Identity -import net.pterodactylus.sone.freenet.wot.OwnIdentity -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.junit.Before -import org.junit.Test - -/** - * Unit test for [KnownSonesPage]. - */ -class KnownSonesPageTest : WebPageTest() { - - private val page = KnownSonesPage(template, webInterface) - - private val sones = listOf( - createSone(1000, 4, 7, 2, "sone2", true, true), - createSone(2000, 3, 2, 3, "Sone1", false, true), - createSone(3000, 3, 8, 1, "Sone3", true, false), - createSone(4000, 1, 6, 0, "sone0", false, false) - ) - - @Before - fun setupSones() { - addSone("sone1", sones[0]) - addSone("sone2", sones[1]) - addSone("sone3", sones[2]) - addSone("sone4", sones[3]) - } - - private fun createSone(time: Long, posts: Int, replies: Int, images: Int, name: String, local: Boolean, new: Boolean) = mock().apply { - whenever(identity).thenReturn(if (local) mock() else mock()) - whenever(isKnown).thenReturn(!new) - whenever(this.time).thenReturn(time) - whenever(this.posts).thenReturn((0..(posts - 1)).map { mock() }) - whenever(this.replies).thenReturn((0..(replies - 1)).map { mock() }.toSet()) - val album = mock() - whenever(album.images).thenReturn(((0..(images - 1)).map { mock() })) - val rootAlbum = mock().apply { - whenever(albums).thenReturn(listOf(album)) - } - whenever(this.rootAlbum).thenReturn(rootAlbum) - whenever(this.profile).thenReturn(mock()) - whenever(id).thenReturn(name.toLowerCase()) - whenever(this.name).thenReturn(name) - } - - private fun verifySonesAreInOrder(vararg indices: Int) { - @Suppress("UNCHECKED_CAST") - assertThat(templateContext["knownSones"] as Iterable, contains( - *indices.map { sones[it] }.toTypedArray() - )) - } - - @Test - fun `default known sones are sorted newest first`() { - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 2, 1, 0) - } - - @Test - fun `known sones can be sorted by oldest first`() { - addHttpRequestParameter("order", "asc") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(0, 1, 2, 3) - } - - @Test - fun `known sones can be sorted by posts, most posts first`() { - addHttpRequestParameter("sort", "posts") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(0, 2, 1, 3) - } - - @Test - fun `known sones can be sorted by posts, least posts first`() { - addHttpRequestParameter("sort", "posts") - addHttpRequestParameter("order", "asc") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 1, 2, 0) - } - - @Test - fun `known sones can be sorted by images, most images first`() { - addHttpRequestParameter("sort", "images") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(1, 0, 2, 3) - } - - @Test - fun `known sones can be sorted by images, least images first`() { - addHttpRequestParameter("sort", "images") - addHttpRequestParameter("order", "asc") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 2, 0, 1) - } - - @Test - fun `known sones can be sorted by nice name, ascending`() { - addHttpRequestParameter("sort", "name") - addHttpRequestParameter("order", "asc") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 1, 0, 2) - } - - @Test - fun `known sones can be sorted by nice name, descending`() { - addHttpRequestParameter("sort", "name") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(2, 0, 1, 3) - } - - @Test - fun `known sones can be filtered by local sones`() { - addHttpRequestParameter("filter", "own") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(2, 0) - } - - @Test - fun `known sones can be filtered by non-local sones`() { - addHttpRequestParameter("filter", "not-own") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 1) - } - - @Test - fun `known sones can be filtered by new sones`() { - addHttpRequestParameter("filter", "new") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(1, 0) - } - - @Test - fun `known sones can be filtered by known sones`() { - addHttpRequestParameter("filter", "not-new") - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 2) - } - - @Test - fun `known sones can be filtered by followed sones`() { - addHttpRequestParameter("filter", "followed") - listOf("sone1", "sone3").forEach { whenever(currentSone.hasFriend(it)).thenReturn(true) } - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(2, 1) - } - - @Test - fun `known sones can be filtered by not-followed sones`() { - addHttpRequestParameter("filter", "not-followed") - listOf("sone1", "sone3").forEach { whenever(currentSone.hasFriend(it)).thenReturn(true) } - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 0) - } - - @Test - fun `known sones can not be filtered by followed sones if there is no current sone`() { - addHttpRequestParameter("filter", "followed") - unsetCurrentSone() - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 2, 1, 0) - } - - @Test - fun `known sones can not be filtered by not-followed sones if there is no current sone`() { - addHttpRequestParameter("filter", "not-followed") - unsetCurrentSone() - page.handleRequest(freenetRequest, templateContext) - verifySonesAreInOrder(3, 2, 1, 0) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/LikePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/LikePageTest.kt deleted file mode 100644 index 52f6174..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/LikePageTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.junit.Test -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions - -/** - * Unit test for [LikePage]. - */ -class LikePageTest : WebPageTest() { - - private val page = LikePage(template, webInterface) - - override fun getPage() = page - - @Test - fun `get request does not redirect`() { - request("", GET) - page.handleRequest(freenetRequest, templateContext) - } - - @Test - fun `post request with post id likes post and redirects to return page`() { - request("", POST) - addHttpRequestParameter("type", "post") - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(currentSone).addLikedPostId("post-id") - } - } - - @Test - fun `post request with reply id likes post and redirects to return page`() { - request("", POST) - addHttpRequestParameter("type", "reply") - addHttpRequestParameter("reply", "reply-id") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(currentSone).addLikedReplyId("reply-id") - } - } - - @Test - fun `post request with invalid likes redirects to return page`() { - request("", POST) - addHttpRequestParameter("type", "foo") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verifyNoMoreInteractions(currentSone) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/LockSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/LockSonePageTest.kt deleted file mode 100644 index 7136e04..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/LockSonePageTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import org.junit.Test -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [LockSonePage]. - */ -class LockSonePageTest : WebPageTest() { - - private val page = LockSonePage(template, webInterface) - - override fun getPage() = page - - @Test - fun `locking an invalid local sone redirects to return page`() { - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(core, never()).lockSone(any()) - } - } - - @Test - fun `locking an valid local sone locks the sone and redirects to return page`() { - addHttpRequestParameter("sone", "sone-id") - val sone = mock() - addLocalSone("sone-id", sone) - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(core).lockSone(sone) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/LoginPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/LoginPageTest.kt deleted file mode 100644 index 1dbe3cd..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/LoginPageTest.kt +++ /dev/null @@ -1,148 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.freenet.wot.Identity -import net.pterodactylus.sone.freenet.wot.OwnIdentity -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.thenReturnMock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.hamcrest.Matchers.containsInAnyOrder -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.nullValue -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [LoginPage]. - */ -class LoginPageTest : WebPageTest() { - - private val page = LoginPage(template, webInterface) - - private val sones = listOf(createSone("Sone", "Test"), createSone("Test"), createSone("Sone")) - - override fun getPage() = page - - private fun createSone(vararg contexts: String) = mock().apply { - whenever(id).thenReturn(hashCode().toString()) - val identity = mock().apply { - whenever(this.contexts).thenReturn(contexts.toSet()) - contexts.forEach { whenever(hasContext(it)).thenReturn(true) } - } - whenever(this.identity).thenReturn(identity) - whenever(profile).thenReturnMock() - } - - @Before - fun setupSones() { - addLocalSone("sone1", sones[0]) - addLocalSone("sone2", sones[1]) - addLocalSone("sone3", sones[2]) - addOwnIdentity(sones[0].identity as OwnIdentity) - addOwnIdentity(sones[1].identity as OwnIdentity) - addOwnIdentity(sones[2].identity as OwnIdentity) - } - - @Test - fun `page returns correct path`() { - assertThat(page.path, equalTo("login.html")) - } - - @Test - fun `page does not require login`() { - assertThat(page.requiresLogin(), equalTo(false)) - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `get request stores sones in template context`() { - request("", GET) - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["sones"] as Iterable, containsInAnyOrder(sones[0], sones[1], sones[2])) - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `get request stores identities without sones in template context`() { - request("", GET) - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["identitiesWithoutSone"] as Iterable, contains(sones[1].identity)) - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `post request with invalid sone sets sones and identities without sone in template context`() { - request("", POST) - page.processTemplate(freenetRequest, templateContext) - assertThat(templateContext["sones"] as Iterable, containsInAnyOrder(sones[0], sones[1], sones[2])) - assertThat(templateContext["identitiesWithoutSone"] as Iterable, contains(sones[1].identity)) - } - - @Test - fun `post request with valid sone logs in the sone and redirects to index page`() { - request("", POST) - addHttpRequestParameter("sone-id", "sone2") - verifyRedirect("index.html") { - verify(webInterface).setCurrentSone(toadletContext, sones[1]) - } - } - - @Test - fun `post request with valid sone and target redirects to target page`() { - request("", POST) - addHttpRequestParameter("sone-id", "sone2") - addHttpRequestParameter("target", "foo.html") - verifyRedirect("foo.html") { - verify(webInterface).setCurrentSone(toadletContext, sones[1]) - } - } - - @Test - fun `redirect to index html if a sone is logged in`() { - assertThat(page.getRedirectTarget(freenetRequest), equalTo("index.html")) - } - - @Test - fun `do not redirect if no sone is logged in`() { - unsetCurrentSone() - assertThat(page.getRedirectTarget(freenetRequest), nullValue()) - } - - @Test - fun `page is not enabled if full access required and request is not full access`() { - core.preferences.isRequireFullAccess = true - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `page is enabled if no full access is required and there is no current sone`() { - unsetCurrentSone() - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - - @Test - fun `page is not enabled if no full access is required but there is a current sone`() { - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `page is enabled if full access required and request is full access and there is no current sone`() { - core.preferences.isRequireFullAccess = true - unsetCurrentSone() - whenever(toadletContext.isAllowedFullAccess).thenReturn(true) - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - - @Test - fun `page is not enabled if full access required and request is full access but there is a current sone`() { - core.preferences.isRequireFullAccess = true - whenever(toadletContext.isAllowedFullAccess).thenReturn(true) - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/LogoutPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/LogoutPageTest.kt deleted file mode 100644 index 8120b50..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/LogoutPageTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.test.whenever -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [LogoutPage]. - */ -class LogoutPageTest : WebPageTest() { - - private val page = LogoutPage(template, webInterface) - - override fun getPage() = page - - @Test - fun `page unsets current sone and redirects to index`() { - verifyRedirect("index.html") { - verify(webInterface).setCurrentSone(toadletContext, null) - } - } - - @Test - fun `page is not enabled if sone requires full access and request does not have full access`() { - core.preferences.isRequireFullAccess = true - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `page is disabled if no sone is logged in`() { - unsetCurrentSone() - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `page is disabled if sone is logged in but there is only one sone`() { - whenever(core.localSones).thenReturn(listOf(currentSone)) - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `page is enabled if sone is logged in and there is more than one sone`() { - whenever(core.localSones).thenReturn(listOf(currentSone, currentSone)) - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - - @Test - fun `page is enabled if full access is required and present and sone is logged in and there is more than one sone`() { - core.preferences.isRequireFullAccess = true - whenever(toadletContext.isAllowedFullAccess).thenReturn(true) - whenever(core.localSones).thenReturn(listOf(currentSone, currentSone)) - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/MarkAsKnownPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/MarkAsKnownPageTest.kt deleted file mode 100644 index c44bbd4..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/MarkAsKnownPageTest.kt +++ /dev/null @@ -1,67 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.PostReply -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [MarkAsKnownPage]. - */ -class MarkAsKnownPageTest : WebPageTest() { - - private val page = MarkAsKnownPage(template, webInterface) - - override fun getPage() = page - - @Test - fun `posts can be marked as known`() { - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("type", "post") - addHttpRequestParameter("id", "post1 post2 post3") - val posts = listOf(mock(), mock()) - addPost("post1", posts[0]) - addPost("post3", posts[1]) - verifyRedirect("return.html") { - verify(core).markPostKnown(posts[0]) - verify(core).markPostKnown(posts[1]) - } - } - - @Test - fun `replies can be marked as known`() { - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("type", "reply") - addHttpRequestParameter("id", "reply1 reply2 reply3") - val replies = listOf(mock(), mock()) - addPostReply("reply1", replies[0]) - addPostReply("reply3", replies[1]) - verifyRedirect("return.html") { - verify(core).markReplyKnown(replies[0]) - verify(core).markReplyKnown(replies[1]) - } - } - - @Test - fun `sones can be marked as known`() { - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("type", "sone") - addHttpRequestParameter("id", "sone1 sone2 sone3") - val sones = listOf(mock(), mock()) - addSone("sone1", sones[0]) - addSone("sone3", sones[1]) - verifyRedirect("return.html") { - verify(core).markSoneKnown(sones[0]) - verify(core).markSoneKnown(sones[1]) - } - } - - @Test - fun `different type redirects to invalid page`() { - addHttpRequestParameter("type", "foo") - verifyRedirect("invalid.html") - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/OptionsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/OptionsPageTest.kt deleted file mode 100644 index cb8a3e5..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/OptionsPageTest.kt +++ /dev/null @@ -1,340 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions -import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.FOLLOWED -import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.TRUSTED -import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired -import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.hasItem -import org.hamcrest.Matchers.nullValue -import org.junit.Before -import org.junit.Test - -/** - * Unit test for [OptionsPage]. - */ -class OptionsPageTest : WebPageTest() { - - private val page = OptionsPage(template, webInterface) - - override fun getPage() = page - - @Before - fun setupPreferences() { - core.preferences.insertionDelay = 1 - core.preferences.charactersPerPost = 50 - core.preferences.fcpFullAccessRequired = WRITING - core.preferences.imagesPerPage = 4 - core.preferences.isFcpInterfaceActive = true - core.preferences.isRequireFullAccess = true - core.preferences.negativeTrust = 7 - core.preferences.positiveTrust = 8 - core.preferences.postCutOffLength = 51 - core.preferences.postsPerPage = 10 - core.preferences.trustComment = "11" - } - - @Before - fun setupSoneOptions() { - whenever(currentSone.options).thenReturn(DefaultSoneOptions().apply { - isAutoFollow = true - isShowNewPostNotifications = true - isShowNewReplyNotifications = true - isShowNewSoneNotifications = true - isSoneInsertNotificationEnabled = true - loadLinkedImages = FOLLOWED - showCustomAvatars = FOLLOWED - }) - } - - @Test - fun `get request stores all preferences in the template context`() { - request("", GET) - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["auto-follow"], equalTo(true)) - assertThat(templateContext["show-notification-new-sones"], equalTo(true)) - assertThat(templateContext["show-notification-new-posts"], equalTo(true)) - assertThat(templateContext["show-notification-new-replies"], equalTo(true)) - assertThat(templateContext["enable-sone-insert-notifications"], equalTo(true)) - assertThat(templateContext["load-linked-images"], equalTo("FOLLOWED")) - assertThat(templateContext["show-custom-avatars"], equalTo("FOLLOWED")) - assertThat(templateContext["insertion-delay"], equalTo(1)) - assertThat(templateContext["characters-per-post"], equalTo(50)) - assertThat(templateContext["fcp-full-access-required"], equalTo(1)) - assertThat(templateContext["images-per-page"], equalTo(4)) - assertThat(templateContext["fcp-interface-active"], equalTo(true)) - assertThat(templateContext["require-full-access"], equalTo(true)) - assertThat(templateContext["negative-trust"], equalTo(7)) - assertThat(templateContext["positive-trust"], equalTo(8)) - assertThat(templateContext["post-cut-off-length"], equalTo(51)) - assertThat(templateContext["posts-per-page"], equalTo(10)) - assertThat(templateContext["trust-comment"], equalTo("11")) - } - - @Test - fun `get request without sone does not store sone-specific preferences in the template context`() { - request("", GET) - unsetCurrentSone() - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["auto-follow"], nullValue()) - assertThat(templateContext["show-notification-new-sones"], nullValue()) - assertThat(templateContext["show-notification-new-posts"], nullValue()) - assertThat(templateContext["show-notification-new-replies"], nullValue()) - assertThat(templateContext["enable-sone-insert-notifications"], nullValue()) - assertThat(templateContext["load-linked-images"], nullValue()) - assertThat(templateContext["show-custom-avatars"], nullValue()) - } - - private fun verifyThatOptionCanBeSet(option: String, setValue: Any?, expectedValue: T, getter: () -> T) { - request("", POST) - addHttpRequestParameter(option, setValue.toString()) - addHttpRequestParameter("show-custom-avatars", "ALWAYS") - addHttpRequestParameter("load-linked-images", "ALWAYS") - verifyRedirect("options.html") { - assertThat(getter(), equalTo(expectedValue)) - } - } - - @Test - fun `auto-follow option can be set`() { - verifyThatOptionCanBeSet("auto-follow", "checked", true) { currentSone.options.isAutoFollow } - } - - @Test - fun `show new sone notification option can be set`() { - verifyThatOptionCanBeSet("show-notification-new-sones", "checked", true) { currentSone.options.isShowNewSoneNotifications } - } - - @Test - fun `show new post notification option can be set`() { - verifyThatOptionCanBeSet("show-notification-new-posts", "checked", true) { currentSone.options.isShowNewPostNotifications } - } - - @Test - fun `show new reply notification option can be set`() { - verifyThatOptionCanBeSet("show-notification-new-replies", "checked", true) { currentSone.options.isShowNewReplyNotifications } - } - - @Test - fun `enable sone insert notifications option can be set`() { - verifyThatOptionCanBeSet("enable-sone-insert-notifications", "checked", true) { currentSone.options.isSoneInsertNotificationEnabled } - } - - @Test - fun `load linked images option can be set`() { - verifyThatOptionCanBeSet("load-linked-images", "TRUSTED", TRUSTED) { currentSone.options.loadLinkedImages } - } - - @Test - fun `show custom avatar option can be set`() { - verifyThatOptionCanBeSet("show-custom-avatars", "TRUSTED", TRUSTED) { currentSone.options.showCustomAvatars } - } - - private fun verifyThatWrongValueForPreferenceIsDetected(name: String, value: String) { - unsetCurrentSone() - request("", POST) - addHttpRequestParameter(name, value) - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["fieldErrors"] as Iterable<*>, hasItem(name)) - } - - private fun verifyThatPreferencesCanBeSet(name: String, setValue: String?, expectedValue: T, getter: () -> T) { - unsetCurrentSone() - request("", POST) - addHttpRequestParameter(name, setValue) - verifyRedirect("options.html") { - assertThat(getter(), equalTo(expectedValue)) - } - } - - @Test - fun `insertion delay can not be set to less than 0 seconds`() { - verifyThatWrongValueForPreferenceIsDetected("insertion-delay", "-1") - } - - @Test - fun `insertion delay can be set to 0 seconds`() { - verifyThatPreferencesCanBeSet("insertion-delay", "0", 0) { core.preferences.insertionDelay } - } - - @Test - fun `setting insertion to an invalid value will reset it`() { - verifyThatPreferencesCanBeSet("insertion-delay", "foo", 60) { core.preferences.insertionDelay } - } - - @Test - fun `characters per post can not be set to less than -1`() { - verifyThatWrongValueForPreferenceIsDetected("characters-per-post", "-2") - } - - @Test - fun `characters per post can be set to -1`() { - verifyThatPreferencesCanBeSet("characters-per-post", "-1", -1) { core.preferences.charactersPerPost } - } - - @Test - fun `characters per post can not be set to 0`() { - verifyThatWrongValueForPreferenceIsDetected("characters-per-post", "0") - } - - @Test - fun `characters per post can not be set to 49`() { - verifyThatWrongValueForPreferenceIsDetected("characters-per-post", "49") - } - - @Test - fun `characters per post can be set to 50`() { - verifyThatPreferencesCanBeSet("characters-per-post", "50", 50) { core.preferences.charactersPerPost } - } - - @Test - fun `fcp full acess required option can be set to always`() { - verifyThatPreferencesCanBeSet("fcp-full-access-required", "2", FullAccessRequired.ALWAYS) { core.preferences.fcpFullAccessRequired } - } - - @Test - fun `fcp full acess required option can be set to writing`() { - verifyThatPreferencesCanBeSet("fcp-full-access-required", "1", FullAccessRequired.WRITING) { core.preferences.fcpFullAccessRequired } - } - - @Test - fun `fcp full acess required option can be set to no`() { - verifyThatPreferencesCanBeSet("fcp-full-access-required", "0", FullAccessRequired.NO) { core.preferences.fcpFullAccessRequired } - } - - @Test - fun `fcp full acess required option is not changed if invalid value is set`() { - verifyThatPreferencesCanBeSet("fcp-full-access-required", "foo", FullAccessRequired.WRITING) { core.preferences.fcpFullAccessRequired } - } - - @Test - fun `images per page can not be set to 0`() { - verifyThatWrongValueForPreferenceIsDetected("images-per-page", "0") - } - - @Test - fun `images per page can be set to 1`() { - verifyThatPreferencesCanBeSet("images-per-page", "1", 1) { core.preferences.imagesPerPage } - } - - @Test - fun `images per page is set to 9 if invalid value is requested`() { - verifyThatPreferencesCanBeSet("images-per-page", "foo", 9) { core.preferences.imagesPerPage } - } - - @Test - fun `fcp interface can be set to true`() { - verifyThatPreferencesCanBeSet("fcp-interface-active", "checked", true) { core.preferences.isFcpInterfaceActive } - } - - @Test - fun `fcp interface can be set to false`() { - verifyThatPreferencesCanBeSet("fcp-interface-active", null, false) { core.preferences.isFcpInterfaceActive } - } - - @Test - fun `require full access can be set to true`() { - verifyThatPreferencesCanBeSet("require-full-access", "checked", true) { core.preferences.isRequireFullAccess } - } - - @Test - fun `require full access can be set to false`() { - verifyThatPreferencesCanBeSet("require-full-access", null, false) { core.preferences.isRequireFullAccess } - } - - @Test - fun `negative trust can not be set to -101`() { - verifyThatWrongValueForPreferenceIsDetected("negative-trust", "-101") - } - - @Test - fun `negative trust can be set to -100`() { - verifyThatPreferencesCanBeSet("negative-trust", "-100", -100) { core.preferences.negativeTrust } - } - - @Test - fun `negative trust can be set to 100`() { - verifyThatPreferencesCanBeSet("negative-trust", "100", 100) { core.preferences.negativeTrust } - } - - @Test - fun `negative trust can not be set to 101`() { - verifyThatWrongValueForPreferenceIsDetected("negative-trust", "101") - } - - @Test - fun `negative trust is set to default on invalid value`() { - verifyThatPreferencesCanBeSet("negative-trust", "invalid", -25) { core.preferences.negativeTrust } - } - - @Test - fun `positive trust can not be set to -1`() { - verifyThatWrongValueForPreferenceIsDetected("positive-trust", "-1") - } - - @Test - fun `positive trust can be set to 0`() { - verifyThatPreferencesCanBeSet("positive-trust", "0", 0) { core.preferences.positiveTrust } - } - - @Test - fun `positive trust can be set to 100`() { - verifyThatPreferencesCanBeSet("positive-trust", "100", 100) { core.preferences.positiveTrust } - } - - @Test - fun `positive trust can not be set to 101`() { - verifyThatWrongValueForPreferenceIsDetected("positive-trust", "101") - } - - @Test - fun `positive trust is set to default on invalid value`() { - verifyThatPreferencesCanBeSet("positive-trust", "invalid", 75) { core.preferences.positiveTrust } - } - - @Test - fun `post cut off length can not be set to -49`() { - verifyThatWrongValueForPreferenceIsDetected("post-cut-off-length", "-49") - } - - @Test - fun `post cut off length can be set to 50`() { - verifyThatPreferencesCanBeSet("post-cut-off-length", "50", 50) { core.preferences.postCutOffLength } - } - - @Test - fun `post cut off length is set to default on invalid value`() { - verifyThatPreferencesCanBeSet("post-cut-off-length", "invalid", 200) { core.preferences.postCutOffLength } - } - - @Test - fun `posts per page can not be set to 0`() { - verifyThatWrongValueForPreferenceIsDetected("posts-per-page", "-49") - } - - @Test - fun `posts per page can be set to 1`() { - verifyThatPreferencesCanBeSet("posts-per-page", "1", 1) { core.preferences.postsPerPage } - } - - @Test - fun `posts per page is set to default on invalid value`() { - verifyThatPreferencesCanBeSet("posts-per-page", "invalid", 10) { core.preferences.postsPerPage } - } - - @Test - fun `trust comment can be set`() { - verifyThatPreferencesCanBeSet("trust-comment", "trust", "trust") { core.preferences.trustComment } - } - - @Test - fun `trust comment is set to default when set to empty value`() { - verifyThatPreferencesCanBeSet("trust-comment", "", "Set from Sone Web Interface") { core.preferences.trustComment } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/ReloadingPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/ReloadingPageTest.kt deleted file mode 100644 index a9cfd46..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/ReloadingPageTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.web.Method.GET -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder -import java.nio.file.Files -import java.nio.file.Paths -import kotlin.text.Charsets.UTF_8 - -/** - * Unit test for [ReloadingPage]. - */ -class ReloadingPageTest : WebPageTest() { - - @Rule @JvmField val tempFolder = TemporaryFolder() - private val folder by lazy { tempFolder.newFolder() } - private val page by lazy { ReloadingPage("/prefix/", folder.path, "text/plain") } - - @Test - fun `page returns correct path prefix`() { - assertThat(page.path, equalTo("/prefix/")) - } - - @Test - fun `page returns that it’s a prefix page`() { - assertThat(page.isPrefixPage, equalTo(true)) - } - - @Test - fun `requesting invalid file results in 404`() { - request("/prefix/path/file.txt", GET) - page.handleRequest(freenetRequest, response) - assertThat(response.statusCode, equalTo(404)) - } - - @Test - fun `requesting valid file results in 200 and delivers file`() { - Files.write(Paths.get(folder.path, "file.txt"), listOf("Hello", "World"), UTF_8) - request("/prefix/path/file.txt", GET) - page.handleRequest(freenetRequest, response) - assertThat(response.statusCode, equalTo(200)) - assertThat(response.contentType, equalTo("text/plain")) - assertThat(responseBytes, equalTo("Hello\nWorld\n".toByteArray())) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/RescuePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/RescuePageTest.kt deleted file mode 100644 index f57d11c..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/RescuePageTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.core.SoneRescuer -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.GET -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.ArgumentMatchers.anyLong -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [RescuePage]. - */ -class RescuePageTest : WebPageTest() { - - private val page = RescuePage(template, webInterface) - - private val soneRescuer = mock() - - override fun getPage() = page - - @Before - fun setupSoneRescuer() { - whenever(core.getSoneRescuer(currentSone)).thenReturn(soneRescuer) - } - - @Test - fun `get request sets rescuer in template context`() { - request("", GET) - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["soneRescuer"], equalTo(soneRescuer)) - } - - @Test - fun `post request redirects to rescue page`() { - request("", POST) - verifyRedirect("rescue.html") - } - - @Test - fun `post request with fetch and invalid edition starts next fetch`() { - request("", POST) - addHttpRequestParameter("fetch", "true") - verifyRedirect("rescue.html") { - verify(soneRescuer, never()).setEdition(anyLong()) - verify(soneRescuer).startNextFetch() - } - } - - @Test - fun `post request with fetch and valid edition sets edition and starts next fetch`() { - request("", POST) - addHttpRequestParameter("fetch", "true") - addHttpRequestParameter("edition", "123") - verifyRedirect("rescue.html") { - verify(soneRescuer).setEdition(123L) - verify(soneRescuer).startNextFetch() - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/SearchPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/SearchPageTest.kt deleted file mode 100644 index 84168f3..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/SearchPageTest.kt +++ /dev/null @@ -1,256 +0,0 @@ -package net.pterodactylus.sone.web - -import com.google.common.base.Optional.absent -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Image -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.PostReply -import net.pterodactylus.sone.data.Profile -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.asOptional -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.junit.Test - -/** - * Unit test for [SearchPage]. - */ -class SearchPageTest : WebPageTest() { - - private val page = SearchPage(template, webInterface) - - override fun getPage() = page - - @Test - fun `empty query redirects to index page`() { - verifyRedirect("index.html") - } - - @Test - fun `empty search phrases redirect to index page`() { - addHttpRequestParameter("query", "\"\"") - verifyRedirect("index.html") - } - - @Test - fun `invalid search phrases redirect to index page`() { - addHttpRequestParameter("query", "\"") - verifyRedirect("index.html") - } - - @Test - fun `searching for sone link redirects to view sone page`() { - addSone("sone-id", mock()) - addHttpRequestParameter("query", "sone://sone-id") - verifyRedirect("viewSone.html?sone=sone-id") - } - - @Test - fun `searching for sone link without prefix redirects to view sone page`() { - addSone("sone-id", mock()) - addHttpRequestParameter("query", "sone-id") - verifyRedirect("viewSone.html?sone=sone-id") - } - - @Test - fun `searching for a post link redirects to post page`() { - addPost("post-id", mock()) - addHttpRequestParameter("query", "post://post-id") - verifyRedirect("viewPost.html?post=post-id") - } - - @Test - fun `searching for a post ID without prefix redirects to post page`() { - addPost("post-id", mock()) - addHttpRequestParameter("query", "post-id") - verifyRedirect("viewPost.html?post=post-id") - } - - @Test - fun `searching for a reply link redirects to the post page`() { - val postReply = mock().apply { whenever(postId).thenReturn("post-id") } - addPostReply("reply-id", postReply) - addHttpRequestParameter("query", "reply://reply-id") - verifyRedirect("viewPost.html?post=post-id") - } - - @Test - fun `searching for a reply ID redirects to the post page`() { - val postReply = mock().apply { whenever(postId).thenReturn("post-id") } - addPostReply("reply-id", postReply) - addHttpRequestParameter("query", "reply-id") - verifyRedirect("viewPost.html?post=post-id") - } - - @Test - fun `searching for an album link redirects to the image browser`() { - addAlbum("album-id", mock()) - addHttpRequestParameter("query", "album://album-id") - verifyRedirect("imageBrowser.html?album=album-id") - } - - @Test - fun `searching for an album ID redirects to the image browser`() { - addAlbum("album-id", mock()) - addHttpRequestParameter("query", "album-id") - verifyRedirect("imageBrowser.html?album=album-id") - } - - @Test - fun `searching for an image link redirects to the image browser`() { - addImage("image-id", mock()) - addHttpRequestParameter("query", "image://image-id") - verifyRedirect("imageBrowser.html?image=image-id") - } - - @Test - fun `searching for an image ID redirects to the image browser`() { - addImage("image-id", mock()) - addHttpRequestParameter("query", "image-id") - verifyRedirect("imageBrowser.html?image=image-id") - } - - private fun createReply(text: String, postId: String? = null, sone: Sone? = null) = mock().apply { - whenever(this.text).thenReturn(text) - postId?.run { whenever(this@apply.postId).thenReturn(postId) } - sone?.run { whenever(this@apply.sone).thenReturn(sone) } - } - - private fun createPost(id: String, text: String) = mock().apply { - whenever(this.id).thenReturn(id) - whenever(recipient).thenReturn(absent()) - whenever(this.text).thenReturn(text) - } - - private fun createSoneWithPost(post: Post, sone: Sone? = null) = sone?.apply { - whenever(posts).thenReturn(listOf(post)) - } ?: mock().apply { - whenever(posts).thenReturn(listOf(post)) - whenever(profile).thenReturn(Profile(this)) - } - - @Test - fun `searching for a single word finds the post`() { - val postWithMatch = createPost("post-with-match", "the word here") - val postWithoutMatch = createPost("post-without-match", "no match here") - val soneWithMatch = createSoneWithPost(postWithMatch) - val soneWithoutMatch = createSoneWithPost(postWithoutMatch) - addSone("sone-with-match", soneWithMatch) - addSone("sone-without-match", soneWithoutMatch) - addHttpRequestParameter("query", "word") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["postHits"], contains(postWithMatch)) - } - - @Test - fun `searching for a single word locates word in reply`() { - val postWithMatch = createPost("post-with-match", "no match here") - val postWithoutMatch = createPost("post-without-match", "no match here") - val soneWithMatch = createSoneWithPost(postWithMatch) - val soneWithoutMatch = createSoneWithPost(postWithoutMatch) - val replyWithMatch = createReply("the word here", "post-with-match", soneWithMatch) - val replyWithoutMatch = createReply("no match here", "post-without-match", soneWithoutMatch) - addPostReply("reply-with-match", replyWithMatch) - addPostReply("reply-without-match", replyWithoutMatch) - addSone("sone-with-match", soneWithMatch) - addSone("sone-without-match", soneWithoutMatch) - addHttpRequestParameter("query", "word") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["postHits"], contains(postWithMatch)) - } - - private fun createSoneWithPost(idPostfix: String, text: String, recipient: Sone? = null, sender: Sone? = null) = - createPost("post-$idPostfix", text, recipient).apply { - addSone("sone-$idPostfix", createSoneWithPost(this, sender)) - } - - @Test - fun `earlier matches score higher than later matches`() { - val postWithEarlyMatch = createSoneWithPost("with-early-match", "optional match") - val postWithLaterMatch = createSoneWithPost("with-later-match", "match that is optional") - addHttpRequestParameter("query", "optional ") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["postHits"], contains(postWithEarlyMatch, postWithLaterMatch)) - } - - @Test - fun `searching for required word does not return posts without that word`() { - val postWithRequiredMatch = createSoneWithPost("with-required-match", "required match") - createPost("without-required-match", "not a match") - addHttpRequestParameter("query", "+required ") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["postHits"], contains(postWithRequiredMatch)) - } - - @Test - fun `searching for forbidden word does not return posts with that word`() { - createSoneWithPost("with-forbidden-match", "forbidden match") - val postWithoutForbiddenMatch = createSoneWithPost("without-forbidden-match", "not a match") - addHttpRequestParameter("query", "match -forbidden") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["postHits"], contains(postWithoutForbiddenMatch)) - } - - @Test - fun `searching for a plus sign searches for optional plus sign`() { - val postWithMatch = createSoneWithPost("with-match", "with + match") - createSoneWithPost("without-match", "without match") - addHttpRequestParameter("query", "+") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["postHits"], contains(postWithMatch)) - } - - @Test - fun `searching for a minus sign searches for optional minus sign`() { - val postWithMatch = createSoneWithPost("with-match", "with - match") - createSoneWithPost("without-match", "without match") - addHttpRequestParameter("query", "-") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["postHits"], contains(postWithMatch)) - } - - private fun createPost(id: String, text: String, recipient: Sone?) = mock().apply { - whenever(this.id).thenReturn(id) - val recipientId = recipient?.id - whenever(this.recipientId).thenReturn(recipientId.asOptional()) - whenever(this.recipient).thenReturn(recipient.asOptional()) - whenever(this.text).thenReturn(text) - } - - private fun createSone(id: String, firstName: String, middleName: String, lastName: String) = mock().apply { - whenever(this.id).thenReturn(id) - whenever(this.name).thenReturn(id) - whenever(this.profile).thenReturn(Profile(this).apply { - this.firstName = firstName - this.middleName = middleName - this.lastName = lastName - }) - } - - @Test - fun `searching for a recipient finds the correct post`() { - val recipient = createSone("recipient", "reci", "pi", "ent") - val postWithMatch = createSoneWithPost("with-match", "test", recipient) - createSoneWithPost("without-match", "no match") - addHttpRequestParameter("query", "recipient") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["postHits"], contains(postWithMatch)) - } - - @Test - fun `searching for a field value finds the correct sone`() { - val soneWithProfileField = createSone("sone", "s", "o", "ne") - soneWithProfileField.profile.addField("field").value = "value" - createSoneWithPost("with-match", "test", sender = soneWithProfileField) - createSoneWithPost("without-match", "no match") - addHttpRequestParameter("query", "value") - page.handleRequest(freenetRequest, templateContext) - assertThat(this["soneHits"], contains(soneWithProfileField)) - } - - @Suppress("UNCHECKED_CAST") - private operator fun get(key: String): T? = templateContext[key] as? T - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/SoneTemplatePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/SoneTemplatePageTest.kt deleted file mode 100644 index 06d4c19..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/SoneTemplatePageTest.kt +++ /dev/null @@ -1,268 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.main.SonePlugin -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.sone.web.page.FreenetRequest -import net.pterodactylus.util.notify.Notification -import net.pterodactylus.util.template.TemplateContext -import net.pterodactylus.util.version.Version -import net.pterodactylus.util.web.Method.GET -import org.hamcrest.Matcher -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.anyOf -import org.hamcrest.Matchers.contains -import org.hamcrest.Matchers.containsInAnyOrder -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.nullValue -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [SoneTemplatePage]. - */ -class SoneTemplatePageTest : WebPageTest() { - - private val preferences by lazy { core.preferences!! } - private val page = object : SoneTemplatePage("path.html", template, webInterface, true) {} - - @Test - fun `current sone is retrieved from web interface`() { - assertThat(page.getCurrentSone(toadletContext), equalTo(currentSone)) - } - - @Test - fun `retrieving current sone without creation is forwarded to web interface`() { - mock().let { - whenever(webInterface.getCurrentSoneWithoutCreatingSession(toadletContext)).thenReturn(it) - assertThat(page.getCurrentSoneWithoutCreatingSession(toadletContext), equalTo(it)) - } - } - - @Test - fun `setting the current sone is forwarded to web interface`() { - mock().let { - page.setCurrentSone(toadletContext, it) - verify(webInterface).setCurrentSone(toadletContext, it) - } - } - - @Test - fun `page title is empty string if no page title key was given`() { - SoneTemplatePage("path.html", template, null, webInterface).let { page -> - assertThat(page.getPageTitle(freenetRequest), equalTo("")) - } - } - - @Test - fun `page title is retrieved from l10n if page title key is given`() { - SoneTemplatePage("path.html", template, "page.title", webInterface).let { page -> - whenever(l10n.getString("page.title")).thenReturn("Page Title") - assertThat(page.getPageTitle(freenetRequest), equalTo("Page Title")) - } - } - - @Test - fun `additional link nodes contain open search link`() { - addHttpRequestHeader("Host", "www.example.com") - assertThat(page.getAdditionalLinkNodes(freenetRequest), contains(mapOf( - "rel" to "search", - "type" to "application/opensearchdescription+xml", - "title" to "Sone", - "href" to "http://www.example.com/Sone/OpenSearch.xml" - ))) - } - - @Test - fun `style sheets contains sone CSS file`() { - assertThat(page.styleSheets, contains("css/sone.css")) - } - - @Test - fun `shortcut icon is the sone icon`() { - assertThat(page.shortcutIcon, equalTo("images/icon.png")) - } - - @Test - fun `page requires login if require login was specified in the constructor`() { - SoneTemplatePage("path.html", template, webInterface, true).let { page -> - assertThat(page.requiresLogin(), equalTo(true)) - } - } - - @Test - fun `page does not require login if require login was not specified in the constructor`() { - SoneTemplatePage("path.html", template, webInterface, false).let { page -> - assertThat(page.requiresLogin(), equalTo(false)) - } - } - - private fun verifyVariableIsSet(name: String, value: Any) = verifyVariableMatches(name, equalTo(value)) - - private fun verifyVariableMatches(name: String, matcher: Matcher) { - page.processTemplate(freenetRequest, templateContext) - @Suppress("UNCHECKED_CAST") - assertThat(templateContext[name] as T, matcher) - } - - @Test - fun `preferences are set in template context`() { - verifyVariableIsSet("preferences", preferences) - } - - @Test - fun `current sone is set in template context`() { - verifyVariableIsSet("currentSone", currentSone) - } - - @Test - fun `local sones are set in template context`() { - val localSones = listOf(mock(), mock()) - whenever(core.localSones).thenReturn(localSones) - verifyVariableMatches("localSones", containsInAnyOrder(*localSones.toTypedArray())) - } - - @Test - fun `freenet request is set in template context`() { - verifyVariableIsSet("request", freenetRequest) - } - - @Test - fun `current version is set in template context`() { - verifyVariableIsSet("currentVersion", SonePlugin.getPluginVersion()) - } - - @Test - fun `has latest version is set correctly in template context if true`() { - whenever(core.updateChecker.hasLatestVersion()).thenReturn(true) - verifyVariableIsSet("hasLatestVersion", true) - } - - @Test - fun `has latest version is set correctly in template context if false`() { - whenever(core.updateChecker.hasLatestVersion()).thenReturn(false) - verifyVariableIsSet("hasLatestVersion", false) - } - - @Test - fun `latest edition is set in template context`() { - whenever(core.updateChecker.latestEdition).thenReturn(1234L) - verifyVariableIsSet("latestEdition", 1234L) - } - - @Test - fun `latest version is set in template context`() { - whenever(core.updateChecker.latestVersion).thenReturn(Version(1, 2, 3)) - verifyVariableIsSet("latestVersion", Version(1, 2, 3)) - } - - @Test - fun `latest version time is set in template context`() { - whenever(core.updateChecker.latestVersionDate).thenReturn(12345L) - verifyVariableIsSet("latestVersionTime", 12345L) - } - - private fun createNotification(time: Long) = mock().apply { - whenever(createdTime).thenReturn(time) - } - - @Test - fun `notifications are set in template context`() { - val notifications = listOf(createNotification(3000), createNotification(1000), createNotification(2000)) - whenever(webInterface.getNotifications(currentSone)).thenReturn(notifications) - verifyVariableMatches("notifications", contains(notifications[1], notifications[2], notifications[0])) - } - - @Test - fun `notification hash is set in template context`() { - val notifications = listOf(createNotification(3000), createNotification(1000), createNotification(2000)) - whenever(webInterface.getNotifications(currentSone)).thenReturn(notifications) - verifyVariableIsSet("notificationHash", listOf(notifications[1], notifications[2], notifications[0]).hashCode()) - } - - @Test - fun `handleRequest method is called`() { - var called = false - val page = object : SoneTemplatePage("path.html", template, webInterface, true) { - override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { - called = true - } - } - page.processTemplate(freenetRequest, templateContext) - assertThat(called, equalTo(true)) - } - - @Test - fun `redirect does not happen if login is not required`() { - val page = SoneTemplatePage("page.html", template, webInterface, false) - assertThat(page.getRedirectTarget(freenetRequest), nullValue()) - } - - @Test - fun `redirect does not happen if sone is logged in`() { - assertThat(page.getRedirectTarget(freenetRequest), nullValue()) - } - - @Test - fun `redirect does happen if sone is not logged in`() { - unsetCurrentSone() - request("index.html", GET) - assertThat(page.getRedirectTarget(freenetRequest), equalTo("login.html?target=index.html")) - } - - @Test - fun `redirect does happen with parameters encoded correctly if sone is not logged in`() { - unsetCurrentSone() - request("index.html", GET) - addHttpRequestParameter("foo", "b=r") - addHttpRequestParameter("baz", "q&o") - assertThat(page.getRedirectTarget(freenetRequest), anyOf( - equalTo("login.html?target=index.html%3Ffoo%3Db%253Dr%26baz%3Dq%2526o"), - equalTo("login.html?target=index.html%3Fbaz%3Dq%2526o%26foo%3Db%253Dr") - )) - } - - @Test - fun `full access requirement is correctly forwarded from the preferences if false`() { - assertThat(page.isFullAccessOnly, equalTo(false)) - } - - @Test - fun `full access requirement is correctly forwarded from the preferences if true`() { - core.preferences.isRequireFullAccess = true - assertThat(page.isFullAccessOnly, equalTo(true)) - } - - @Test - fun `page is disabled if full access is required but request does not have full access`() { - core.preferences.isRequireFullAccess = true - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `page is disabled if login is required but there is no current sone`() { - unsetCurrentSone() - assertThat(page.isEnabled(toadletContext), equalTo(false)) - } - - @Test - fun `page is enabled if login is required and there is a current sone`() { - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - - @Test - fun `page is enabled if full access is required and request has full access and login is required and there is a current sone`() { - core.preferences.isRequireFullAccess = true - whenever(toadletContext.isAllowedFullAccess).thenReturn(true) - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - - @Test - fun `page is enabled if no full access is required and login is not required`() { - SoneTemplatePage("path.html", template, webInterface, false).let { page -> - assertThat(page.isEnabled(toadletContext), equalTo(true)) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/TrustPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/TrustPageTest.kt deleted file mode 100644 index a29c0c2..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/TrustPageTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.util.web.Method.POST -import org.junit.Test -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [TrustPage]. - */ -class TrustPageTest : WebPageTest() { - - private val page = TrustPage(template, webInterface) - - override fun getPage() = page - - @Test - fun `get method does not redirect`() { - page.handleRequest(freenetRequest, templateContext) - } - - @Test - fun `post request with missing sone redirects to return page`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "sone-id") - verifyRedirect("return.html") { - verify(core, never()).trustSone(eq(currentSone), any()) - } - } - - @Test - fun `post request with existing sone trusts the identity and redirects to return page`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "sone-id") - val sone = mock() - addSone("sone-id", sone) - verifyRedirect("return.html") { - verify(core).trustSone(eq(currentSone), eq(sone)) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/UnbookmarkPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/UnbookmarkPageTest.kt deleted file mode 100644 index d599f19..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/UnbookmarkPageTest.kt +++ /dev/null @@ -1,64 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.POST -import org.junit.Test -import org.mockito.Mockito.any -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [UnbookmarkPage]. - */ -class UnbookmarkPageTest : WebPageTest() { - - private val page = UnbookmarkPage(template, webInterface) - - override fun getPage() = page - - @Test - fun `get request does not redirect`() { - page.handleRequest(freenetRequest, templateContext) - } - - @Test - fun `get request with all-not-loaded parameter unloads all not loaded posts and redirects to bookmarks`() { - addHttpRequestParameter("post", "allNotLoaded") - val loadedPost1 = mock().apply { whenever(isLoaded).thenReturn(true) } - val loadedPost2 = mock().apply { whenever(isLoaded).thenReturn(true) } - val notLoadedPost1 = mock() - val notLoadedPost2 = mock() - whenever(core.bookmarkedPosts).thenReturn(setOf(loadedPost1, loadedPost2, notLoadedPost1, notLoadedPost2)) - verifyRedirect("bookmarks.html") { - verify(core).unbookmarkPost(notLoadedPost1) - verify(core).unbookmarkPost(notLoadedPost2) - verify(core, never()).unbookmarkPost(loadedPost1) - verify(core, never()).unbookmarkPost(loadedPost2) - } - } - - @Test - fun `post request does not unbookmark not-present post but redirects to return page`() { - request("", POST) - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(core, never()).unbookmarkPost(any()) - } - } - - @Test - fun `post request unbookmarks present post and redirects to return page`() { - request("", POST) - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("returnPage", "return.html") - val post = mock().apply { whenever(isLoaded).thenReturn(true) } - addPost("post-id", post) - verifyRedirect("return.html") { - verify(core).unbookmarkPost(post) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/UnfollowSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/UnfollowSonePageTest.kt deleted file mode 100644 index b9c3433..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/UnfollowSonePageTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.util.web.Method.POST -import org.junit.Test -import org.mockito.Mockito.verify - -/** - * Unit test for [UnfollowSonePage]. - */ -class UnfollowSonePageTest : WebPageTest() { - - private val page = UnfollowSonePage(template, webInterface) - - override fun getPage() = page - - @Test - fun `get request does not redirect`() { - page.handleRequest(freenetRequest, templateContext) - } - - @Test - fun `post request unfollows a single sone and redirects to return page`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "sone-id") - verifyRedirect("return.html") { - verify(core).unfollowSone(currentSone, "sone-id") - } - } - - @Test - fun `post request unfollows two sones and redirects to return page`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "sone-id1, sone-id2") - verifyRedirect("return.html") { - verify(core).unfollowSone(currentSone, "sone-id1") - verify(core).unfollowSone(currentSone, "sone-id2") - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/UnlikePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/UnlikePageTest.kt deleted file mode 100644 index 3bbd1e4..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/UnlikePageTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.util.web.Method -import net.pterodactylus.util.web.Method.POST -import org.junit.Test -import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [UnlikePage]. - */ -class UnlikePageTest : WebPageTest() { - - private val page = UnlikePage(template, webInterface) - - override fun getPage() = page - - @Test - fun `get request does not redirect`() { - page.handleRequest(freenetRequest, templateContext) - } - - @Test - fun `post request does not remove any likes but redirects`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(currentSone, never()).removeLikedPostId(any()) - verify(currentSone, never()).removeLikedReplyId(any()) - } - } - - @Test - fun `post request removes post like and redirects`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("type", "post") - addHttpRequestParameter("id", "post-id") - verifyRedirect("return.html") { - verify(currentSone, never()).removeLikedPostId("post-id") - verify(currentSone, never()).removeLikedReplyId(any()) - } - } - - @Test - fun `post request removes reply like and redirects`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("type", "reply") - addHttpRequestParameter("id", "reply-id") - verifyRedirect("return.html") { - verify(currentSone, never()).removeLikedPostId(any()) - verify(currentSone, never()).removeLikedReplyId("reply-id") - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/UnlockSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/UnlockSonePageTest.kt deleted file mode 100644 index 4785156..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/UnlockSonePageTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import org.junit.Test -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [UnlockSonePage]. - */ -class UnlockSonePageTest : WebPageTest() { - - private val page = UnlockSonePage(template, webInterface) - - override fun getPage() = page - - @Test - fun `get request without sone redirects to return page`() { - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(core, never()).unlockSone(any()) - } - } - - @Test - fun `get request without invalid local sone does not unlock any sone and redirects to return page`() { - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "invalid-sone") - verifyRedirect("return.html") { - verify(core, never()).unlockSone(any()) - } - } - - @Test - fun `get request without remote sone does not unlock any sone and redirects to return page`() { - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "remote-sone") - addSone("remote-sone", mock()) - verifyRedirect("return.html") { - verify(core, never()).unlockSone(any()) - } - } - - @Test - fun `get request with local sone unlocks sone and redirects to return page`() { - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "local-sone") - val sone = mock().apply { whenever(isLocal).thenReturn(true) } - addLocalSone("local-sone", sone) - verifyRedirect("return.html") { - verify(core).unlockSone(sone) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/UntrustPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/UntrustPageTest.kt deleted file mode 100644 index 166fd34..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/UntrustPageTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock -import net.pterodactylus.util.web.Method.POST -import org.junit.Test -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [UntrustPage]. - */ -class UntrustPageTest : WebPageTest() { - - private val page = UntrustPage(template, webInterface) - - override fun getPage() = page - - @Test - fun `get request does not redirect`() { - page.handleRequest(freenetRequest, templateContext) - verify(core, never()).untrustSone(eq(currentSone), any()) - } - - @Test - fun `post request without sone parameter does not untrust but redirects`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - verifyRedirect("return.html") { - verify(core, never()).untrustSone(eq(currentSone), any()) - } - } - - @Test - fun `post request with invalid sone parameter does not untrust but redirects`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "no-sone") - verifyRedirect("return.html") { - verify(core, never()).untrustSone(eq(currentSone), any()) - } - } - - @Test - fun `post request with valid sone parameter untrusts and redirects`() { - request("", POST) - addHttpRequestParameter("returnPage", "return.html") - addHttpRequestParameter("sone", "sone-id") - val sone = mock() - addSone("sone-id", sone) - verifyRedirect("return.html") { - verify(core).untrustSone(currentSone, sone) - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/UploadImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/UploadImagePageTest.kt deleted file mode 100644 index 0eb34d6..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/UploadImagePageTest.kt +++ /dev/null @@ -1,99 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Album -import net.pterodactylus.sone.data.Image -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.data.TemporaryImage -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.mockBuilder -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.web.Method.POST -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test -import org.mockito.Mockito.any -import org.mockito.Mockito.eq -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [UploadImagePage]. - */ -class UploadImagePageTest : WebPageTest() { - - private val parentAlbum = mock().apply { - whenever(id).thenReturn("parent-id") - whenever(sone).thenReturn(currentSone) - } - - override fun getPage() = UploadImagePage(template, webInterface) - - @Test - fun `get request does not redirect or upload anything`() { - page.handleRequest(freenetRequest, templateContext) - verify(core, never()).createTemporaryImage(any(), any()) - verify(core, never()).createImage(any(), any(), any()) - } - - @Test - fun `post request without parent results in no permission error page`() { - request("", POST) - verifyRedirect("noPermission.html") - } - - @Test - fun `post request with parent that is not the current sone results in no permission error page`() { - request("", POST) - addHttpRequestParameter("parent", "parent-id") - whenever(parentAlbum.sone).thenReturn(mock()) - addAlbum("parent-id", parentAlbum) - verifyRedirect("noPermission.html") - } - - @Test - fun `post request with empty name redirects to error page`() { - request("", POST) - addAlbum("parent-id", parentAlbum) - addHttpRequestParameter("parent", "parent-id") - addHttpRequestParameter("title", " ") - verifyRedirect("emptyImageTitle.html") - } - - @Test - fun `uploading an invalid image results in no redirect and message set in template context`() { - request("", POST) - addAlbum("parent-id", parentAlbum) - addHttpRequestParameter("parent", "parent-id") - addHttpRequestParameter("title", "title") - addUploadedFile("image", "image.png", "image/png", "no-image.png") - page.handleRequest(freenetRequest, templateContext) - verify(core, never()).createTemporaryImage(any(), any()) - assertThat(templateContext["messages"] as String?, equalTo("Page.UploadImage.Error.InvalidImage")) - } - - @Test - fun `uploading a valid image uploads image and redirects to album browser`() { - request("", POST) - addAlbum("parent-id", parentAlbum) - addHttpRequestParameter("parent", "parent-id") - addHttpRequestParameter("title", "Title") - addHttpRequestParameter("description", "Description") - addUploadedFile("image", "image.png", "image/png", "image.png") - val temporaryImage = TemporaryImage("temp-image") - val imageModifier = mockBuilder() - val image = mock().apply { - whenever(modify()).thenReturn(imageModifier) - } - whenever(core.createTemporaryImage(eq("image/png"), any())).thenReturn(temporaryImage) - whenever(core.createImage(currentSone, parentAlbum, temporaryImage)).thenReturn(image) - verifyRedirect("imageBrowser.html?album=parent-id") { - verify(image).modify() - verify(imageModifier).setWidth(2) - verify(imageModifier).setHeight(1) - verify(imageModifier).setTitle("Title") - verify(imageModifier).setDescription("Description") - verify(imageModifier).update() - } - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/ViewPostPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/ViewPostPageTest.kt deleted file mode 100644 index 36c70d2..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/ViewPostPageTest.kt +++ /dev/null @@ -1,86 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.Profile -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.nullValue -import org.junit.Test - -/** - * Unit test for [ViewPostPage]. - */ -class ViewPostPageTest : WebPageTest() { - - private val page = ViewPostPage(template, webInterface) - private val post = mock() - - override fun getPage() = page - - @Test - fun `the view post page is link-excepted`() { - assertThat(page.isLinkExcepted(null), equalTo(true)) - } - - @Test - fun `get request without parameters stores null in template context`() { - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["post"], nullValue()) - assertThat(templateContext["raw"] as? Boolean, equalTo(false)) - } - - @Test - fun `get request with invalid post id stores null in template context`() { - addHttpRequestParameter("post", "invalid-post-id") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["post"], nullValue()) - assertThat(templateContext["raw"] as? Boolean, equalTo(false)) - } - - @Test - fun `get request with valid post id stores post in template context`() { - addPost("post-id", post) - addHttpRequestParameter("post", "post-id") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["post"], equalTo(post)) - assertThat(templateContext["raw"] as? Boolean, equalTo(false)) - } - - @Test - fun `get request with valid post id and raw=true stores post in template context`() { - addPost("post-id", post) - addHttpRequestParameter("post", "post-id") - addHttpRequestParameter("raw", "true") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["post"], equalTo(post)) - assertThat(templateContext["raw"] as? Boolean, equalTo(true)) - } - - @Test - fun `page title for request without parameters is default title`() { - assertThat(page.getPageTitle(freenetRequest), equalTo("Page.ViewPost.Title")) - } - - @Test - fun `page title for request with invalid post is default title`() { - addHttpRequestParameter("post", "invalid-post-id") - assertThat(page.getPageTitle(freenetRequest), equalTo("Page.ViewPost.Title")) - } - - @Test - fun `page title for request with valid post is first twenty chars of post plus sone name plus default title`() { - whenever(currentSone.profile).thenReturn(Profile(currentSone).apply { - firstName = "First" - middleName = "M." - lastName = "Last" - }) - whenever(post.sone).thenReturn(currentSone) - whenever(post.text).thenReturn("This is a text that is longer than twenty characters.") - addPost("post-id", post) - addHttpRequestParameter("post", "post-id") - assertThat(page.getPageTitle(freenetRequest), equalTo("This is a text that … - First M. Last - Page.ViewPost.Title")) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/ViewSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/ViewSonePageTest.kt deleted file mode 100644 index 428da7b..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/web/ViewSonePageTest.kt +++ /dev/null @@ -1,155 +0,0 @@ -package net.pterodactylus.sone.web - -import net.pterodactylus.sone.data.Post -import net.pterodactylus.sone.data.PostReply -import net.pterodactylus.sone.data.Profile -import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.asOptional -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.contains -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.nullValue -import org.junit.Before -import org.junit.Test - -/** - * Unit test for [ViewSonePage]. - */ -class ViewSonePageTest : WebPageTest() { - - init { - whenever(currentSone.id).thenReturn("sone-id") - } - - private val page = ViewSonePage(template, webInterface) - private val post1 = createPost("post1", "First Post.", 1000, currentSone) - private val post2 = createPost("post2", "Second Post.", 2000, currentSone) - private val foreignPost1 = createPost("foreign-post1", "First Foreign Post.", 1000, mock()) - private val foreignPost2 = createPost("foreign-post2", "Second Foreign Post.", 2000, mock()) - private val directed1 = createPost("post3", "First directed.", 1500, mock(), recipient = currentSone) - private val directed2 = createPost("post4", "Second directed.", 2500, mock(), recipient = currentSone) - - override fun getPage() = page - - @Before - fun setup() { - whenever(currentSone.posts).thenReturn(mutableListOf(post2, post1)) - whenever(core.getDirectedPosts("sone-id")).thenReturn(setOf(directed1, directed2)) - core.preferences.postsPerPage = 2 - } - - @Test - fun `get request without sone parameter stores null in template context`() { - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["sone"], nullValue()) - assertThat(templateContext["soneId"], equalTo("")) - } - - @Test - fun `get request with invalid sone parameter stores null in template context`() { - addHttpRequestParameter("sone", "invalid-sone-id") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["sone"], nullValue()) - assertThat(templateContext["soneId"], equalTo("invalid-sone-id")) - } - - @Test - fun `get request with valid sone parameter stores sone in template context`() { - whenever(currentSone.posts).thenReturn(mutableListOf()) - whenever(core.getDirectedPosts("sone-id")).thenReturn(emptyList()) - addHttpRequestParameter("sone", "sone-id") - addSone("sone-id", currentSone) - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["sone"], equalTo(currentSone)) - assertThat(templateContext["soneId"], equalTo("sone-id")) - } - - private fun createPost(id: String, text: String, time: Long, sender: Sone? = null, recipient: Sone? = null) = mock().apply { - whenever(this.id).thenReturn(id) - sender?.run { whenever(this@apply.sone).thenReturn(this) } - val recipientId = recipient?.id - whenever(this.recipientId).thenReturn(recipientId.asOptional()) - whenever(this.recipient).thenReturn(recipient.asOptional()) - whenever(this.time).thenReturn(time) - whenever(this.text).thenReturn(text) - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `request with valid sone stores posts and directed posts in template context`() { - addSone("sone-id", currentSone) - addHttpRequestParameter("sone", "sone-id") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["posts"] as Iterable, contains(directed2, post2)) - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `second page of posts is shown correctly`() { - addSone("sone-id", currentSone) - addHttpRequestParameter("sone", "sone-id") - addHttpRequestParameter("postPage", "1") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["posts"] as Iterable, contains(directed1, post1)) - } - - private fun createReply(text: String, time: Long, post: Post?) = mock().apply { - whenever(this.text).thenReturn(text) - whenever(this.time).thenReturn(time) - whenever(this.post).thenReturn(post.asOptional()) - } - - @Test - @Suppress("UNCHECKED_CAST") - fun `replies are shown correctly`() { - val reply1 = createReply("First Reply", 1500, foreignPost1) - val reply2 = createReply("Second Reply", 2500, foreignPost2) - val reply3 = createReply("Third Reply", 1750, post1) - val reply4 = createReply("Fourth Reply", 2250, post2) - val reply5 = createReply("Fifth Reply", 1600, post1) - val reply6 = createReply("Sixth Reply", 2100, directed1) - val reply7 = createReply("Seventh Reply", 2200, null) - val reply8 = createReply("Eigth Reply", 2300, foreignPost1) - whenever(currentSone.replies).thenReturn(setOf(reply1, reply2, reply3, reply4, reply5, reply6, reply7, reply8)) - whenever(core.getReplies("post1")).thenReturn(listOf(reply3, reply5)) - whenever(core.getReplies("post2")).thenReturn(listOf(reply4)) - whenever(core.getReplies("foreign-post1")).thenReturn(listOf(reply1)) - whenever(core.getReplies("foreign-post2")).thenReturn(listOf(reply2)) - whenever(core.getReplies("post3")).thenReturn(listOf(reply6)) - addSone("sone-id", currentSone) - addHttpRequestParameter("sone", "sone-id") - page.handleRequest(freenetRequest, templateContext) - assertThat(templateContext["repliedPosts"] as Iterable, contains(foreignPost2, foreignPost1)) - } - - @Test - fun `page title is default for request without parameters`() { - assertThat(page.getPageTitle(freenetRequest), equalTo("Page.ViewSone.Page.TitleWithoutSone")) - } - - @Test - fun `page title is default for request with invalid sone parameters`() { - addHttpRequestParameter("sone", "invalid-sone-id") - assertThat(page.getPageTitle(freenetRequest), equalTo("Page.ViewSone.Page.TitleWithoutSone")) - } - - @Test - fun `page title contains sone name for request with sone parameters`() { - addHttpRequestParameter("sone", "sone-id") - addSone("sone-id", currentSone) - whenever(currentSone.profile).thenReturn(Profile(currentSone).apply { - firstName = "First" - middleName = "M." - lastName = "Last" - }) - assertThat(page.getPageTitle(freenetRequest), equalTo("First M. Last - Page.ViewSone.Title")) - } - - @Test - fun `page is link-excepted`() { - assertThat(page.isLinkExcepted(null), equalTo(true)) - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/AboutPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/AboutPageTest.kt new file mode 100644 index 0000000..93672a1 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/AboutPageTest.kt @@ -0,0 +1,50 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.main.SonePlugin.PluginHomepage +import net.pterodactylus.sone.main.SonePlugin.PluginVersion +import net.pterodactylus.sone.main.SonePlugin.PluginYear +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.AboutPage +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test + +/** + * Unit test for [AboutPage]. + */ +class AboutPageTest: WebPageTest() { + + private val version = "0.1.2" + private val year = 1234 + private val homepage = "home://page" + private val page = AboutPage(template, webInterface, PluginVersion(version), PluginYear(year), PluginHomepage(homepage)) + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("about.html")) + } + + @Test + fun `page does not require login`() { + assertThat(page.requiresLogin(), equalTo(false)) + } + + @Test + fun `page sets correct version in template context`() { + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["version"], equalTo(version)) + } + + @Test + fun `page sets correct homepage in template context`() { + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["homepage"], equalTo(homepage)) + } + + @Test + fun `page sets correct year in template context`() { + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["year"], equalTo(year)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarkPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarkPageTest.kt new file mode 100644 index 0000000..b5d10cc --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarkPageTest.kt @@ -0,0 +1,58 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.BookmarkPage +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [BookmarkPage]. + */ +class BookmarkPageTest : WebPageTest() { + + private val page = BookmarkPage(template, webInterface) + override fun getPage() = page + + @Test + fun `path is set correctly`() { + assertThat(page.path, equalTo("bookmark.html")) + } + + @Test + fun `get request does not bookmark anything and does not redirect`() { + page.processTemplate(freenetRequest, templateContext) + verify(core, never()).bookmarkPost(any()) + } + + private fun setupBookmarkRequest() { + request("", POST) + addHttpRequestParameter("returnPage", "return-page.html") + addHttpRequestParameter("post", "post-id") + } + + @Test + fun `post is bookmarked correctly`() { + setupBookmarkRequest() + val post = mock() + addPost("post-id", post) + verifyRedirect("return-page.html") { + verify(core).bookmarkPost(post) + } + } + + @Test + fun `non-existing post is not bookmarked`() { + setupBookmarkRequest() + verifyRedirect("return-page.html") { + verify(core, never()).bookmarkPost(any()) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarksPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarksPageTest.kt new file mode 100644 index 0000000..6b4bbae --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/BookmarksPageTest.kt @@ -0,0 +1,60 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.BookmarksPage +import net.pterodactylus.util.collection.Pagination +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test + +/** + * Unit test for [BookmarksPage]. + */ +class BookmarksPageTest: WebPageTest() { + + private val page = BookmarksPage(template, webInterface) + private val post1 = createLoadedPost(1000) + private val post2 = createLoadedPost(3000) + private val post3 = createLoadedPost(2000) + + private fun createLoadedPost(time: Long) = mock().apply { + whenever(isLoaded).thenReturn(true) + whenever(this.time).thenReturn(time) + } + + @Before + fun setupBookmarkedPostsAndPagination() { + whenever(core.bookmarkedPosts).thenReturn(setOf(post1, post2, post3)) + core.preferences.postsPerPage = 5 + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("bookmarks.html")) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `page sets correct posts in template context`() { + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["posts"] as Collection, contains(post2, post3, post1)) + assertThat((templateContext["pagination"] as Pagination).items, contains(post2, post3, post1)) + assertThat(templateContext["postsNotLoaded"], equalTo(false)) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `page does not put unloaded posts in template context but sets a flag`() { + whenever(post3.isLoaded).thenReturn(false) + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["posts"] as Collection, contains(post2, post1)) + assertThat((templateContext["pagination"] as Pagination).items, contains(post2, post1)) + assertThat(templateContext["postsNotLoaded"], equalTo(true)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPageTest.kt new file mode 100644 index 0000000..c4f2ee4 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPageTest.kt @@ -0,0 +1,105 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Album.Modifier +import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty +import net.pterodactylus.sone.test.deepMock +import net.pterodactylus.sone.test.selfMock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.CreateAlbumPage +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [CreateAlbumPage]. + */ +class CreateAlbumPageTest: WebPageTest() { + + private val page = CreateAlbumPage(template, webInterface) + + override fun getPage() = page + + private val parentAlbum = createAlbum("parent-id") + private val newAlbum = createAlbum("album-id") + + @Before + fun setupAlbums() { + whenever(core.createAlbum(currentSone, parentAlbum)).thenReturn(newAlbum) + whenever(currentSone.rootAlbum).thenReturn(parentAlbum) + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("createAlbum.html")) + } + + @Test + fun `get request shows template`() { + page.processTemplate(freenetRequest, templateContext) + } + + @Test + fun `missing name results in attribute being set in template context`() { + request("", POST) + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["nameMissing"], equalTo(true)) + } + + private fun createAlbum(albumId: String) = deepMock().apply { + whenever(id).thenReturn(albumId) + selfMock().let { modifier -> + whenever(modifier.update()).thenReturn(this@apply) + whenever(this@apply.modify()).thenReturn(modifier) + } + } + + @Test + fun `title and description are set correctly on the album`() { + request("", POST) + addAlbum("parent-id", parentAlbum) + addHttpRequestParameter("name", "new name") + addHttpRequestParameter("description", "new description") + addHttpRequestParameter("parent", "parent-id") + verifyRedirect("imageBrowser.html?album=album-id") { + verify(newAlbum).modify() + verify(newAlbum.modify()).setTitle("new name") + verify(newAlbum.modify()).setDescription("new description") + verify(newAlbum.modify()).update() + verify(core).touchConfiguration() + } + } + + @Test + fun `root album is used if no parent is specified`() { + request("", POST) + addHttpRequestParameter("name", "new name") + addHttpRequestParameter("description", "new description") + verifyRedirect("imageBrowser.html?album=album-id") + } + + @Test + fun `empty album title redirects to error page`() { + request("", POST) + whenever(newAlbum.modify().update()).thenThrow(AlbumTitleMustNotBeEmpty::class.java) + addHttpRequestParameter("name", "new name") + addHttpRequestParameter("description", "new description") + verifyRedirect("emptyAlbumTitle.html") + } + + @Test + fun `album description is filtered`() { + request("", POST) + addHttpRequestParameter("name", "new name") + addHttpRequestParameter("description", "new http://localhost:12345/KSK@foo description") + addHttpRequestHeader("Host", "localhost:12345") + verifyRedirect("imageBrowser.html?album=album-id") { + verify(newAlbum.modify()).setDescription("new KSK@foo description") + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/CreatePostPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/CreatePostPageTest.kt new file mode 100644 index 0000000..45a2451 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/CreatePostPageTest.kt @@ -0,0 +1,97 @@ +package net.pterodactylus.sone.web.pages + +import com.google.common.base.Optional.absent +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.asOptional +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.CreatePostPage +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [CreatePostPage]. + */ +class CreatePostPageTest: WebPageTest() { + + private val page = CreatePostPage(template, webInterface) + + override fun getPage() = page + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("createPost.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `return page is set in template context`() { + addHttpRequestParameter("returnPage", "return.html") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["returnPage"], equalTo("return.html")) + } + + @Test + fun `post is created correctly`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("text", "post text") + verifyRedirect("return.html") { + verify(core).createPost(currentSone, absent(), "post text") + } + } + + @Test + fun `creating an empty post is denied`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("text", " ") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["errorTextEmpty"], equalTo(true)) + } + + @Test + fun `a sender can be selected`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("text", "post text") + addHttpRequestParameter("sender", "sender-id") + val sender = mock() + addLocalSone("sender-id", sender) + verifyRedirect("return.html") { + verify(core).createPost(sender, absent(), "post text") + } + } + + @Test + fun `a recipient can be selected`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("text", "post text") + addHttpRequestParameter("recipient", "recipient-id") + val recipient = mock() + addSone("recipient-id", recipient) + verifyRedirect("return.html") { + verify(core).createPost(currentSone, recipient.asOptional(), "post text") + } + } + + @Test + fun `text is filtered correctly`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("text", "post http://localhost:12345/KSK@foo text") + addHttpRequestHeader("Host", "localhost:12345") + verifyRedirect("return.html") { + verify(core).createPost(currentSone, absent(), "post KSK@foo text") + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPageTest.kt new file mode 100644 index 0000000..052620a --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPageTest.kt @@ -0,0 +1,104 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.CreateReplyPage +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [CreateReplyPage]. + */ +class CreateReplyPageTest: WebPageTest() { + + private val page = CreateReplyPage(template, webInterface) + override fun getPage() = page + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("createReply.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `reply is created correctly`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("text", "new text") + val post = mock().apply { addPost("post-id", this) } + verifyRedirect("return.html") { + verify(core).createReply(currentSone, post, "new text") + } + } + + @Test + fun `reply is filtered`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("text", "new http://localhost:12345/KSK@foo text") + addHttpRequestHeader("Host", "localhost:12345") + val post = mock().apply { addPost("post-id", this) } + verifyRedirect("return.html") { + verify(core).createReply(currentSone, post, "new KSK@foo text") + } + } + + @Test + fun `reply is created with correct sender`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("text", "new text") + addHttpRequestParameter("sender", "sender-id") + val sender = mock().apply { addLocalSone("sender-id", this) } + val post = mock().apply { addPost("post-id", this) } + verifyRedirect("return.html") { + verify(core).createReply(sender, post, "new text") + } + } + + @Test + fun `empty text sets parameters in template contexty`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("text", " ") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["errorTextEmpty"], equalTo(true)) + assertThat(templateContext["returnPage"], equalTo("return.html")) + assertThat(templateContext["postId"], equalTo("post-id")) + assertThat(templateContext["text"], equalTo("")) + } + + @Test + fun `user is redirected to no permissions page if post does not exist`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("text", "new text") + verifyRedirect("noPermission.html") + } + + @Test + fun `get request stores parameters in template context`() { + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("text", "new text") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["returnPage"], equalTo("return.html")) + assertThat(templateContext["postId"], equalTo("post-id")) + assertThat(templateContext["text"], equalTo("new text")) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateSonePageTest.kt new file mode 100644 index 0000000..f14d536 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/CreateSonePageTest.kt @@ -0,0 +1,150 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Profile +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.freenet.wot.OwnIdentity +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.CreateSonePage +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.verify + +/** + * Unit test for [CreateSonePage]. + */ +class CreateSonePageTest: WebPageTest() { + + private val page = CreateSonePage(template, webInterface) + override fun getPage() = page + + private val localSones_ = listOf( + createSone("local-sone1"), + createSone("local-sone2"), + createSone("local-sone3") + ) + + private fun createSone(id: String) = mock().apply { + whenever(this.id).thenReturn(id) + whenever(profile).thenReturn(Profile(this)) + } + + private val ownIdentities_ = listOf( + createOwnIdentity("own-id-1", "Sone"), + createOwnIdentity("own-id-2", "Test", "Foo"), + createOwnIdentity("own-id-3"), + createOwnIdentity("own-id-4", "Sone") + ) + + private fun createOwnIdentity(id: String, vararg contexts: String) = mock().apply { + whenever(this.id).thenReturn(id) + whenever(this.nickname).thenReturn(id) + whenever(this.contexts).thenReturn(contexts.toSet()) + whenever(this.hasContext(anyString())).thenAnswer { invocation -> invocation.getArgument(0) in contexts } + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("createSone.html")) + } + + @Test + fun `page does not require login`() { + assertThat(page.requiresLogin(), equalTo(false)) + } + + private fun addExistingSones() { + listOf(2, 0, 1).map { localSones_[it] }.forEach { addLocalSone(it.id, it) } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `get request stores sorted list of local sones in template context`() { + addExistingSones() + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["sones"] as Collection, contains(localSones_[0], localSones_[1], localSones_[2])) + } + + private fun addExistingOwnIdentities() { + listOf(2, 0, 3, 1).map { ownIdentities_[it] }.forEach { addOwnIdentity(it) } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `get request stores sorted sones without sone context in the template context`() { + addExistingOwnIdentities() + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["identitiesWithoutSone"] as Collection, contains(ownIdentities_[1], ownIdentities_[2])) + } + + @Test + fun `sone is created and logged in`() { + addExistingOwnIdentities() + request("", POST) + addHttpRequestParameter("identity", "own-id-3") + val newSone = mock() + whenever(core.createSone(ownIdentities_[2])).thenReturn(newSone) + verifyRedirect("index.html") { + verify(webInterface).setCurrentSone(toadletContext, newSone) + } + } + + @Test + fun `on invalid identity id a flag is set in the template context`() { + request("", POST) + addHttpRequestParameter("identity", "own-id-3") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["errorNoIdentity"], equalTo(true)) + } + + @Test + fun `if sone is not created user is still redirected to index`() { + addExistingOwnIdentities() + request("", POST) + addHttpRequestParameter("identity", "own-id-3") + whenever(core.createSone(ownIdentities_[2])).thenReturn(null) + verifyRedirect("index.html") { + verify(core).createSone(ownIdentities_[2]) + verify(webInterface).setCurrentSone(toadletContext, null) + } + } + + @Test + fun `create sone is not shown in menu if full access is required but client doesn’t have full access`() { + core.preferences.isRequireFullAccess = true + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `create sone is shown in menu if no sone is logged in`() { + unsetCurrentSone() + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + + @Test + fun `create sone is shown in menu if a single sone exists`() { + addLocalSone("local-sone", localSones_[0]) + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + + @Test + fun `create sone is not shown in menu if more than one sone exists`() { + addLocalSone("local-sone1", localSones_[0]) + addLocalSone("local-sone2", localSones_[1]) + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `create sone is shown in menu if no sone is logged in and client has full access`() { + core.preferences.isRequireFullAccess = true + whenever(toadletContext.isAllowedFullAccess).thenReturn(true) + unsetCurrentSone() + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPageTest.kt new file mode 100644 index 0000000..3efd109 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPageTest.kt @@ -0,0 +1,116 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.DeleteAlbumPage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.verify + +/** + * Unit test for [DeleteAlbumPage]. + */ +class DeleteAlbumPageTest: WebPageTest() { + + private val page = DeleteAlbumPage(template, webInterface) + + private val sone = mock() + private val album = mock() + private val parentAlbum = mock() + + override fun getPage() = page + + @Before + fun setupAlbums() { + whenever(sone.id).thenReturn("sone-id") + whenever(sone.isLocal).thenReturn(true) + whenever(parentAlbum.id).thenReturn("parent-id") + whenever(parentAlbum.isRoot).thenReturn(true) + whenever(album.id).thenReturn("album-id") + whenever(album.sone).thenReturn(sone) + whenever(album.parent).thenReturn(parentAlbum) + whenever(sone.rootAlbum).thenReturn(parentAlbum) + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("deleteAlbum.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `get request with invalid album ID results in redirect to invalid page`() { + request("", GET) + whenever(core.getAlbum(anyString())).thenReturn(null) + verifyRedirect("invalid.html") + } + + @Test + fun `get request with valid album ID sets album in template context`() { + request("", GET) + val album = mock() + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["album"], equalTo(album)) + } + + @Test + fun `post request redirects to invalid page if album is invalid`() { + request("", POST) + verifyRedirect("invalid.html") + } + + @Test + fun `post request redirects to no permissions page if album is not local`() { + request("", POST) + whenever(sone.isLocal).thenReturn(false) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + verifyRedirect("noPermission.html") + } + + @Test + fun `post request with abort delete parameter set redirects to album browser`() { + request("", POST) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + addHttpRequestParameter("abortDelete", "true") + verifyRedirect("imageBrowser.html?album=album-id") + } + + @Test + fun `album is deleted and page redirects to sone if parent album is root album`() { + request("", POST) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + verifyRedirect("imageBrowser.html?sone=sone-id") { + verify(core).deleteAlbum(album) + } + } + + @Test + fun `album is deleted and page redirects to album if parent album is not root album`() { + request("", POST) + whenever(parentAlbum.isRoot).thenReturn(false) + whenever(sone.rootAlbum).thenReturn(mock()) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + verifyRedirect("imageBrowser.html?album=parent-id") { + verify(core).deleteAlbum(album) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePageTest.kt new file mode 100644 index 0000000..78000a1 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePageTest.kt @@ -0,0 +1,92 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Image +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.DeleteImagePage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [DeleteImagePage]. + */ +class DeleteImagePageTest: WebPageTest() { + + private val page = DeleteImagePage(template, webInterface) + private val image = mock() + private val sone = mock() + + override fun getPage() = page + + @Before + fun setupImage() { + val album = mock() + whenever(album.id).thenReturn("album-id") + whenever(image.id).thenReturn("image-id") + whenever(image.sone).thenReturn(sone) + whenever(image.album).thenReturn(album) + whenever(sone.isLocal).thenReturn(true) + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("deleteImage.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `get request with invalid image redirects to invalid page`() { + request("", GET) + verifyRedirect("invalid.html") + } + + @Test + fun `get request with image from non-local sone redirects to no permissions page`() { + request("", GET) + whenever(sone.isLocal).thenReturn(false) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + verifyRedirect("noPermission.html") + } + + @Test + fun `get request with image from local sone sets image in template context`() { + request("", GET) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["image"], equalTo(image)) + } + + @Test + fun `post request with abort delete flag set redirects to image browser`() { + request("", POST) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + addHttpRequestParameter("abortDelete", "true") + verifyRedirect("imageBrowser.html?image=image-id") + } + + @Test + fun `post request deletes image and redirects to image browser`() { + request("", POST) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + verifyRedirect("imageBrowser.html?album=album-id") { + verify(webInterface.core).deleteImage(image) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DeletePostPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeletePostPageTest.kt new file mode 100644 index 0000000..152e70d --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeletePostPageTest.kt @@ -0,0 +1,114 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.DeletePostPage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [DeletePostPage]. + */ +class DeletePostPageTest : WebPageTest() { + + private val page = DeletePostPage(template, webInterface) + + private val post = mock() + private val sone = mock() + + override fun getPage() = page + + @Before + fun setupPost() { + whenever(post.sone).thenReturn(sone) + whenever(sone.isLocal).thenReturn(true) + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("deletePost.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `get request with invalid post redirects to no permission page`() { + request("", GET) + verifyRedirect("noPermission.html") + } + + @Test + fun `get request with valid post sets post and return page in template context`() { + request("", GET) + addPost("post-id", post) + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("returnPage", "return.html") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["post"], equalTo(post)) + assertThat(templateContext["returnPage"], equalTo("return.html")) + } + + @Test + fun `post request with invalid post redirects to no permission page`() { + request("", POST) + verifyRedirect("noPermission.html") + } + + @Test + fun `post request with post from non-local sone redirects to no permission page`() { + request("", POST) + whenever(sone.isLocal).thenReturn(false) + addPost("post-id", post) + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("noPermission.html") + } + + @Test + fun `post request with confirmation deletes post and redirects to return page`() { + request("", POST) + addPost("post-id", post) + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("confirmDelete", "true") + verifyRedirect("return.html") { + verify(core).deletePost(post) + } + } + + @Test + fun `post request with abort delete does not delete post and redirects to return page`() { + request("", POST) + addPost("post-id", post) + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("abortDelete", "true") + verifyRedirect("return.html") { + verify(core, never()).deletePost(post) + } + } + + @Test + fun `post request without delete or abort sets post in template context`() { + request("", POST) + addPost("post-id", post) + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("returnPage", "return.html") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["post"], equalTo(post)) + assertThat(templateContext["returnPage"], equalTo("return.html")) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPageTest.kt new file mode 100644 index 0000000..ebfadcc --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPageTest.kt @@ -0,0 +1,87 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Profile +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.DeleteProfileFieldPage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.nullValue +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.any +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [DeleteProfileFieldPage]. + */ +class DeleteProfileFieldPageTest: WebPageTest() { + + private val page = DeleteProfileFieldPage(template, webInterface) + + private val profile = Profile(currentSone) + private val field = profile.addField("name") + + override fun getPage() = page + + @Before + fun setupProfile() { + whenever(currentSone.profile).thenReturn(profile) + field.value = "value" + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("deleteProfileField.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `get request with invalid field name redirects to invalid page`() { + request("", GET) + verifyRedirect("invalid.html") + } + + @Test + fun `post request with invalid field name redirects to invalid page`() { + request("", POST) + addHttpRequestParameter("field", "wrong-id") + verifyRedirect("invalid.html") + } + + @Test + fun `get request with valid field name sets field in template context`() { + request("", GET) + addHttpRequestParameter("field", field.id) + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["field"], equalTo(field)) + } + + @Test + fun `post request without confirm redirects to edit profile page`() { + request("", POST) + addHttpRequestParameter("field", field.id) + verifyRedirect("editProfile.html#profile-fields") { + verify(currentSone, never()).profile = any() + } + } + + @Test + fun `post request with confirm removes field and redirects to edit profile page`() { + request("", POST) + addHttpRequestParameter("field", field.id) + addHttpRequestParameter("confirm", "true") + verifyRedirect("editProfile.html#profile-fields") { + assertThat(profile.getFieldById(field.id), nullValue()) + verify(currentSone).profile = profile + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPageTest.kt new file mode 100644 index 0000000..ff65e9d --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPageTest.kt @@ -0,0 +1,106 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.PostReply +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.DeleteReplyPage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [DeleteReplyPage]. + */ +class DeleteReplyPageTest : WebPageTest() { + + private val page = DeleteReplyPage(template, webInterface) + + private val sone = mock() + private val reply = mock() + + override fun getPage() = page + + @Before + fun setupReply() { + whenever(sone.isLocal).thenReturn(true) + whenever(reply.sone).thenReturn(sone) + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("deleteReply.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `get request sets reply ID and return page in template context`() { + request("", GET) + addHttpRequestParameter("reply", "reply-id") + addHttpRequestParameter("returnPage", "return.html") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["reply"], equalTo("reply-id")) + assertThat(templateContext["returnPage"], equalTo("return.html")) + } + + @Test + fun `post request without any action sets reply ID and return page in template context`() { + request("", POST) + addPostReply("reply-id", reply) + addHttpRequestParameter("reply", "reply-id") + addHttpRequestParameter("returnPage", "return.html") + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["reply"], equalTo("reply-id")) + assertThat(templateContext["returnPage"], equalTo("return.html")) + } + + @Test + fun `trying to delete a reply with an invalid ID results in no permission page`() { + request("", POST) + verifyRedirect("noPermission.html") + } + + @Test + fun `trying to delete a reply from a non-local sone results in no permission page`() { + request("", POST) + addHttpRequestParameter("reply", "reply-id") + whenever(sone.isLocal).thenReturn(false) + addPostReply("reply-id", reply) + verifyRedirect("noPermission.html") + } + + @Test + fun `confirming deletion of reply deletes the reply and redirects to return page`() { + request("", POST) + addPostReply("reply-id", reply) + addHttpRequestParameter("reply", "reply-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("confirmDelete", "true") + verifyRedirect("return.html") { + verify(core).deleteReply(reply) + } + } + + @Test + fun `aborting deletion of reply redirects to return page`() { + request("", POST) + addPostReply("reply-id", reply) + addHttpRequestParameter("reply", "reply-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("abortDelete", "true") + verifyRedirect("return.html") { + verify(core, never()).deleteReply(reply) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt new file mode 100644 index 0000000..b94c3fa --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt @@ -0,0 +1,65 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.DeleteSonePage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.any +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [DeleteSonePage]. + */ +class DeleteSonePageTest : WebPageTest() { + + private val page = DeleteSonePage(template, webInterface) + + override fun getPage() = page + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("deleteSone.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `page returns correct title`() { + whenever(l10n.getString("Page.DeleteSone.Title")).thenReturn("delete sone page") + assertThat(page.getPageTitle(freenetRequest), equalTo("delete sone page")) + } + + @Test + fun `get request does not redirect`() { + request("", GET) + page.processTemplate(freenetRequest, templateContext) + } + + @Test + fun `post request without delete confirmation redirects to index`() { + request("", POST) + verifyRedirect("index.html") { + verify(core, never()).deleteSone(any()) + } + } + + @Test + fun `post request with delete confirmation deletes sone and redirects to index`() { + request("", POST) + addHttpRequestParameter("deleteSone", "true") + verifyRedirect("index.html") { + verify(core).deleteSone(currentSone) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt new file mode 100644 index 0000000..b6524a8 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt @@ -0,0 +1,71 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.DismissNotificationPage +import net.pterodactylus.util.notify.Notification +import net.pterodactylus.util.web.Method.GET +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [DismissNotificationPage]. + */ +class DismissNotificationPageTest: WebPageTest() { + + private val page = DismissNotificationPage(template, webInterface) + private val notification = mock() + + override fun getPage() = page + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("dismissNotification.html")) + } + + @Test + fun `page does not require login`() { + assertThat(page.requiresLogin(), equalTo(false)) + } + + @Test + fun `page returns correct title`() { + whenever(l10n.getString("Page.DismissNotification.Title")).thenReturn("dismiss notification page") + assertThat(page.getPageTitle(freenetRequest), equalTo("dismiss notification page")) + } + + @Test + fun `get request with invalid notification ID redirects to return page`() { + request("", GET) + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") + } + + @Test + fun `get request with non-dismissible notification never dismisses the notification but redirects to return page`() { + request("", GET) + addNotification("notification-id", notification) + addHttpRequestParameter("notification", "notification-id") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(notification, never()).dismiss() + } + } + + @Test + fun `get request with dismissible notification dismisses the notification and redirects to return page`() { + request("", GET) + whenever(notification.isDismissable).thenReturn(true) + addNotification("notification-id", notification) + addHttpRequestParameter("notification", "notification-id") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(notification).dismiss() + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt new file mode 100644 index 0000000..45498c2 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt @@ -0,0 +1,65 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.DistrustPage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [DistrustPage]. + */ +class DistrustPageTest: WebPageTest() { + + private val page = DistrustPage(template, webInterface) + + override fun getPage() = page + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("distrust.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `page returns correct title`() { + whenever(l10n.getString("Page.Distrust.Title")).thenReturn("distrust page title") + assertThat(page.getPageTitle(freenetRequest), equalTo("distrust page title")) + } + + @Test + fun `get request does not redirect`() { + request("", GET) + page.processTemplate(freenetRequest, templateContext) + } + + @Test + fun `post request with invalid sone redirects to return page`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") + } + + @Test + fun `post request with valid sone distrusts sone and redirects to return page`() { + request("", POST) + val remoteSone = mock() + addSone("remote-sone-id", remoteSone) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "remote-sone-id") + verifyRedirect("return.html") { + verify(core).distrustSone(currentSone, remoteSone) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt new file mode 100644 index 0000000..c9bd4fb --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt @@ -0,0 +1,131 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Album.Modifier +import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.mockBuilder +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.EditAlbumPage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [EditAlbumPage]. + */ +class EditAlbumPageTest: WebPageTest() { + + private val page = EditAlbumPage(template, webInterface) + + private val album = mock() + private val parentAlbum = mock() + private val modifier = mockBuilder() + private val sone = mock() + + override fun getPage() = page + + @Before + fun setup() { + whenever(album.id).thenReturn("album-id") + whenever(album.sone).thenReturn(sone) + whenever(album.parent).thenReturn(parentAlbum) + whenever(album.modify()).thenReturn(modifier) + whenever(modifier.update()).thenReturn(album) + whenever(parentAlbum.id).thenReturn("parent-id") + whenever(sone.isLocal).thenReturn(true) + addHttpRequestHeader("Host", "www.te.st") + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("editAlbum.html")) + } + + @Test + fun `page requires login`() { + assertThat(page.requiresLogin(), equalTo(true)) + } + + @Test + fun `page returns correct title`() { + whenever(l10n.getString("Page.EditAlbum.Title")).thenReturn("edit album page") + assertThat(page.getPageTitle(freenetRequest), equalTo("edit album page")) + } + + @Test + fun `get request does not redirect`() { + request("", GET) + page.processTemplate(freenetRequest, templateContext) + } + + @Test + fun `post request with invalid album redirects to invalid page`() { + request("", POST) + verifyRedirect("invalid.html") + } + + @Test + fun `post request with album of non-local sone redirects to no permissions page`() { + request("", POST) + whenever(sone.isLocal).thenReturn(false) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + verifyRedirect("noPermission.html") + } + + @Test + fun `post request with move left requested moves album to the left and redirects to album browser`() { + request("", POST) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + addHttpRequestParameter("moveLeft", "true") + verifyRedirect("imageBrowser.html?album=parent-id") { + verify(parentAlbum).moveAlbumUp(album) + verify(core).touchConfiguration() + } + } + + @Test + fun `post request with move right requested moves album to the left and redirects to album browser`() { + request("", POST) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + addHttpRequestParameter("moveRight", "true") + verifyRedirect("imageBrowser.html?album=parent-id") { + verify(parentAlbum).moveAlbumDown(album) + verify(core).touchConfiguration() + } + } + + @Test + fun `post request with empty album title redirects to empty album title page`() { + request("", POST) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + whenever(modifier.setTitle("")).thenThrow(AlbumTitleMustNotBeEmpty()) + verifyRedirect("emptyAlbumTitle.html") + } + + @Test + fun `post request with non-empty album title and description redirects to album browser`() { + request("", POST) + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + addHttpRequestParameter("title", "title") + addHttpRequestParameter("description", "description") + verifyRedirect("imageBrowser.html?album=album-id") { + verify(modifier).setTitle("title") + verify(modifier).setDescription("description") + verify(modifier).update() + verify(core).touchConfiguration() + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt new file mode 100644 index 0000000..d36dec5 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt @@ -0,0 +1,135 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Image +import net.pterodactylus.sone.data.Image.Modifier +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.mockBuilder +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.EditImagePage +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [EditImagePage]. + */ +class EditImagePageTest : WebPageTest() { + + private val page = EditImagePage(template, webInterface) + + private val image = mock() + private val modifier = mockBuilder() + private val sone = mock() + private val album = mock() + + override fun getPage() = page + + @Before + fun setupImage() { + whenever(sone.isLocal).thenReturn(true) + whenever(album.id).thenReturn("album-id") + whenever(modifier.update()).thenReturn(image) + whenever(image.sone).thenReturn(sone) + whenever(image.album).thenReturn(album) + whenever(image.modify()).thenReturn(modifier) + } + + @Test + fun `get request does not redirect`() { + request("", GET) + page.handleRequest(freenetRequest, templateContext) + } + + @Test + fun `post request with invalid image redirects to invalid page`() { + request("", POST) + verifyRedirect("invalid.html") + } + + @Test + fun `post request with valid image from non-local sone redirects to no permission page`() { + request("", POST) + whenever(sone.isLocal).thenReturn(false) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + verifyRedirect("noPermission.html") + } + + @Test + fun `post request with valid image and move left requested moves image left and redirects to return page`() { + request("", POST) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("moveLeft", "true") + verifyRedirect("return.html") { + verify(album).moveImageUp(image) + verify(core).touchConfiguration() + } + } + + @Test + fun `post request with valid image and move right requested moves image right and redirects to return page`() { + request("", POST) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("moveRight", "true") + verifyRedirect("return.html") { + verify(album).moveImageDown(image) + verify(core).touchConfiguration() + } + } + + @Test + fun `post request with valid image but only whitespace in the title redirects to empty image title page`() { + request("", POST) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("title", " ") + verifyRedirect("emptyImageTitle.html") { + verify(core, never()).touchConfiguration() + } + } + + @Test + fun `post request with valid image title and description modifies image and redirects to reutrn page`() { + request("", POST) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("title", "Title") + addHttpRequestParameter("description", "Description") + verifyRedirect("return.html") { + verify(modifier).setTitle("Title") + verify(modifier).setDescription("Description") + verify(modifier).update() + verify(core).touchConfiguration() + } + } + + @Test + fun `post request with image title and description modifies image with filtered description and redirects to reutrn page`() { + request("", POST) + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("title", "Title") + addHttpRequestHeader("Host", "www.te.st") + addHttpRequestParameter("description", "Get http://www.te.st/KSK@GPL.txt") + verifyRedirect("return.html") { + verify(modifier).setTitle("Title") + verify(modifier).setDescription("Get KSK@GPL.txt") + verify(modifier).update() + verify(core).touchConfiguration() + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt new file mode 100644 index 0000000..cbe1289 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt @@ -0,0 +1,89 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Profile +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.EditProfileFieldPage +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [EditProfileFieldPage]. + */ +class EditProfileFieldPageTest : WebPageTest() { + + private val page = EditProfileFieldPage(template, webInterface) + + private val profile = Profile(currentSone) + private val field = profile.addField("Name") + + override fun getPage() = page + + @Before + fun setupProfile() { + whenever(currentSone.profile).thenReturn(profile) + } + + @Test + fun `get request with invalid field redirects to invalid page`() { + request("", GET) + verifyRedirect("invalid.html") + } + + @Test + fun `get request with valid field stores field in template context`() { + request("", GET) + addHttpRequestParameter("field", field.id) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["field"], equalTo(field)) + } + + @Test + fun `post request with cancel set redirects to profile edit page`() { + request("", POST) + addHttpRequestParameter("field", field.id) + addHttpRequestParameter("cancel", "true") + verifyRedirect("editProfile.html#profile-fields") + } + + @Test + fun `post request with new name renames field and redirects to profile edit page`() { + request("", POST) + addHttpRequestParameter("field", field.id) + addHttpRequestParameter("name", "New Name") + verifyRedirect("editProfile.html#profile-fields") { + assertThat(field.name, equalTo("New Name")) + verify(currentSone).profile = profile + } + } + + @Test + fun `post request with same name does not modify field and redirects to profile edit page`() { + request("", POST) + addHttpRequestParameter("field", field.id) + addHttpRequestParameter("name", "Name") + verifyRedirect("editProfile.html#profile-fields") { + assertThat(field.name, equalTo("Name")) + verify(currentSone, never()).profile = profile + } + } + + @Test + fun `post request with same name as different field sets error condition in template`() { + request("", POST) + profile.addField("New Name") + addHttpRequestParameter("field", field.id) + addHttpRequestParameter("name", "New Name") + page.handleRequest(freenetRequest, templateContext) + assertThat(field.name, equalTo("Name")) + verify(currentSone, never()).profile = profile + assertThat(templateContext["duplicateFieldName"], equalTo(true)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt new file mode 100644 index 0000000..c79bbcf --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt @@ -0,0 +1,212 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Image +import net.pterodactylus.sone.data.Profile +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.EditProfilePage +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.notNullValue +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [EditProfilePage]. + */ +class EditProfilePageTest : WebPageTest() { + + private val page = EditProfilePage(template, webInterface) + + private val profile = Profile(currentSone) + private val firstField = profile.addField("First Field") + private val secondField = profile.addField("Second Field") + + override fun getPage() = page + + @Before + fun setupProfile() { + val avatar = mock() + whenever(avatar.id).thenReturn("image-id") + whenever(avatar.sone).thenReturn(currentSone) + profile.firstName = "First" + profile.middleName = "Middle" + profile.lastName = "Last" + profile.birthDay = 31 + profile.birthMonth = 12 + profile.birthYear = 1999 + profile.setAvatar(avatar) + whenever(currentSone.profile).thenReturn(profile) + } + + @Test + fun `get request stores fields of current sone’s profile in template context`() { + request("", GET) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["firstName"], equalTo("First")) + assertThat(templateContext["middleName"], equalTo("Middle")) + assertThat(templateContext["lastName"], equalTo("Last")) + assertThat(templateContext["birthDay"], equalTo(31)) + assertThat(templateContext["birthMonth"], equalTo(12)) + assertThat(templateContext["birthYear"], equalTo(1999)) + assertThat(templateContext["avatarId"], equalTo("image-id")) + assertThat(templateContext["fields"], equalTo(listOf(firstField, secondField))) + } + + @Test + fun `post request without any command stores fields of current sone’s profile in template context`() { + request("", POST) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["firstName"], equalTo("First")) + assertThat(templateContext["middleName"], equalTo("Middle")) + assertThat(templateContext["lastName"], equalTo("Last")) + assertThat(templateContext["birthDay"], equalTo(31)) + assertThat(templateContext["birthMonth"], equalTo(12)) + assertThat(templateContext["birthYear"], equalTo(1999)) + assertThat(templateContext["avatarId"], equalTo("image-id")) + assertThat(templateContext["fields"], equalTo(listOf(firstField, secondField))) + } + + private fun verifySingleFieldCanBeChanged(fieldName: String, newValue: T, expectedValue: T = newValue, fieldAccessor: () -> T) { + request("", POST) + addHttpRequestParameter("save-profile", "true") + addHttpRequestParameter(fieldName, newValue.toString()) + verifyRedirect("editProfile.html") { + verify(core).touchConfiguration() + assertThat(fieldAccessor(), equalTo(expectedValue)) + } + } + + @Test + fun `post request with new first name and save profile saves the profile and redirects back to profile edit page`() { + verifySingleFieldCanBeChanged("first-name", "New First") { profile.firstName } + } + + @Test + fun `post request with new middle name and save profile saves the profile and redirects back to profile edit page`() { + verifySingleFieldCanBeChanged("middle-name", "New Middle") { profile.middleName } + } + + @Test + fun `post request with new last name and save profile saves the profile and redirects back to profile edit page`() { + verifySingleFieldCanBeChanged("last-name", "New Last") { profile.lastName } + } + + @Test + fun `post request with new birth day and save profile saves the profile and redirects back to profile edit page`() { + verifySingleFieldCanBeChanged("birth-day", 1) { profile.birthDay } + } + + @Test + fun `post request with new birth month and save profile saves the profile and redirects back to profile edit page`() { + verifySingleFieldCanBeChanged("birth-month", 1) { profile.birthMonth } + } + + @Test + fun `post request with new birth year and save profile saves the profile and redirects back to profile edit page`() { + verifySingleFieldCanBeChanged("birth-year", 1) { profile.birthYear } + } + + @Test + fun `post request with new avatar ID and save profile saves the profile and redirects back to profile edit page`() { + val newAvatar = mock() + whenever(newAvatar.sone).thenReturn(currentSone) + whenever(newAvatar.id).thenReturn("avatar-id") + addImage("avatar-id", newAvatar) + verifySingleFieldCanBeChanged("avatarId", "avatar-id") { profile.avatar } + } + + @Test + fun `post request with field value saves profile and redirects back to profile edit page`() { + val field = profile.addField("name") + field.value = "old" + verifySingleFieldCanBeChanged("field-${field.id}", "new") { profile.getFieldByName("name")!!.value } + } + + @Test + fun `post request with field value saves filtered value to profile and redirects back to profile edit page`() { + val field = profile.addField("name") + field.value = "old" + addHttpRequestHeader("Host", "www.te.st") + verifySingleFieldCanBeChanged("field-${field.id}", "http://www.te.st/KSK@GPL.txt", "KSK@GPL.txt") { profile.getFieldByName("name")!!.value } + } + + @Test + fun `adding a field with a duplicate name sets error in template context`() { + request("", POST) + profile.addField("new-field") + addHttpRequestParameter("add-field", "true") + addHttpRequestParameter("field-name", "new-field") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["fieldName"], equalTo("new-field")) + assertThat(templateContext["duplicateFieldName"], equalTo(true)) + verify(core, never()).touchConfiguration() + } + + @Test + fun `adding a field with a new name sets adds field to profile and redirects to profile edit page`() { + request("", POST) + addHttpRequestParameter("add-field", "true") + addHttpRequestParameter("field-name", "new-field") + verifyRedirect("editProfile.html#profile-fields") { + assertThat(profile.getFieldByName("new-field"), notNullValue()) + verify(currentSone).profile = profile + verify(core).touchConfiguration() + } + } + + @Test + fun `deleting a field redirects to delete field page`() { + request("", POST) + addHttpRequestParameter("delete-field-${firstField.id}", "true") + verifyRedirect("deleteProfileField.html?field=${firstField.id}") + } + + @Test + fun `moving a field up moves the field up and redirects to the edit profile page`() { + request("", POST) + addHttpRequestParameter("move-up-field-${secondField.id}", "true") + verifyRedirect("editProfile.html#profile-fields") { + assertThat(profile.fields, contains(secondField, firstField)) + verify(currentSone).profile = profile + } + } + + @Test + fun `moving an invalid field up redirects to the invalid page`() { + request("", POST) + addHttpRequestParameter("move-up-field-foo", "true") + verifyRedirect("invalid.html") + } + + @Test + fun `moving a field down moves the field down and redirects to the edit profile page`() { + request("", POST) + addHttpRequestParameter("move-down-field-${firstField.id}", "true") + verifyRedirect("editProfile.html#profile-fields") { + assertThat(profile.fields, contains(secondField, firstField)) + verify(currentSone).profile = profile + } + } + + @Test + fun `moving an invalid field down redirects to the invalid page`() { + request("", POST) + addHttpRequestParameter("move-down-field-foo", "true") + verifyRedirect("invalid.html") + } + + @Test + fun `editing a field redirects to the edit profile page`() { + request("", POST) + addHttpRequestParameter("edit-field-${firstField.id}", "true") + verifyRedirect("editProfileField.html?field=${firstField.id}") + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt new file mode 100644 index 0000000..f595b4a --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt @@ -0,0 +1,72 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.web.pages.FollowSonePage +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.junit.Test +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [FollowSonePage]. + */ +class FollowSonePageTest : WebPageTest() { + + private val page = FollowSonePage(template, webInterface) + + override fun getPage() = page + + @Test + fun `get request does not redirect`() { + request("", GET) + page.handleRequest(freenetRequest, templateContext) + } + + @Test + fun `a single sone can be followed`() { + request("", POST) + val sone = mock() + addSone("sone-id", sone) + addHttpRequestParameter("sone", "sone-id") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(core).followSone(currentSone, "sone-id") + verify(core).markSoneKnown(sone) + } + } + + @Test + fun `multiple sones can be followed`() { + request("", POST) + val firstSone = mock() + addSone("sone-id1", firstSone) + val secondSone = mock() + addSone("sone-id2", secondSone) + addHttpRequestParameter("sone", "sone-id1,sone-id2") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(core).followSone(currentSone, "sone-id1") + verify(core).followSone(currentSone, "sone-id2") + verify(core).markSoneKnown(firstSone) + verify(core).markSoneKnown(secondSone) + } + } + + @Test + fun `a non-existing sone is not followed`() { + request("", POST) + addHttpRequestParameter("sone", "sone-id") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(core, never()).followSone(ArgumentMatchers.eq(currentSone), anyString()) + verify(core, never()).markSoneKnown(any()) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/GetImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/GetImagePageTest.kt new file mode 100644 index 0000000..fb96a4c --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/GetImagePageTest.kt @@ -0,0 +1,53 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.TemporaryImage +import net.pterodactylus.sone.web.pages.GetImagePage +import net.pterodactylus.sone.web.pages.WebPageTest +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test + +/** + * Unit test for [GetImagePage]. + */ +class GetImagePageTest : WebPageTest() { + + private val page = GetImagePage(webInterface) + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("getImage.html")) + } + + @Test + fun `page is not a prefix page`() { + assertThat(page.isPrefixPage, equalTo(false)) + } + + @Test + fun `page is not link-excepted`() { + assertThat(page.isLinkExcepted(null), equalTo(false)) + } + + @Test + fun `invalid image returns 404 response`() { + page.handleRequest(freenetRequest, response) + assertThat(response.statusCode, equalTo(404)) + assertThat(responseBytes, equalTo(ByteArray(0))) + } + + @Test + fun `valid image returns response with correct data`() { + val image = TemporaryImage("temp-id").apply { + mimeType = "image/test" + imageData = ByteArray(5, Int::toByte) + } + addHttpRequestParameter("image", "temp-id") + addTemporaryImage("temp-id", image) + page.handleRequest(freenetRequest, response) + assertThat(response.statusCode, equalTo(200)) + assertThat(response.contentType, equalTo("image/test")) + assertThat(responseBytes, equalTo(ByteArray(5, Int::toByte))) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt new file mode 100644 index 0000000..7a12242 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt @@ -0,0 +1,106 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Image +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.ImageBrowserPage +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.util.web.Method.GET +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.equalTo +import org.junit.Test + +/** + * Unit test for [ImageBrowserPage]. + */ +class ImageBrowserPageTest : WebPageTest() { + + private val page = ImageBrowserPage(template, webInterface) + + @Test + fun `get request with album sets album and page in template context`() { + request("", GET) + val album = mock() + addAlbum("album-id", album) + addHttpRequestParameter("album", "album-id") + addHttpRequestParameter("page", "5") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["albumRequested"], equalTo(true)) + assertThat(templateContext["album"], equalTo(album)) + assertThat(templateContext["page"], equalTo("5")) + } + + @Test + fun `get request with image sets image in template context`() { + request("", GET) + val image = mock() + addImage("image-id", image) + addHttpRequestParameter("image", "image-id") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["imageRequested"], equalTo(true)) + assertThat(templateContext["image"], equalTo(image)) + } + + @Test + fun `get request with sone sets sone in template context`() { + request("", GET) + val sone = mock() + addSone("sone-id", sone) + addHttpRequestParameter("sone", "sone-id") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["soneRequested"], equalTo(true)) + assertThat(templateContext["sone"], equalTo(sone)) + } + + @Test + fun `get request with mode of gallery sets albums and page in template context`() { + request("", GET) + val firstSone = createSone("first album", "second album") + addSone("sone1", firstSone) + val secondSone = createSone("third album", "fourth album") + addSone("sone2", secondSone) + addHttpRequestParameter("mode", "gallery") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["galleryRequested"], equalTo(true)) + @Suppress("UNCHECKED_CAST") + assertThat(templateContext["albums"] as Iterable, contains( + firstSone.rootAlbum.albums[0], + secondSone.rootAlbum.albums[1], + firstSone.rootAlbum.albums[1], + secondSone.rootAlbum.albums[0] + )) + } + + private fun createSone(firstAlbumTitle: String, secondAlbumTitle: String): Sone { + return mock().apply { + val rootAlbum = mock() + val firstAlbum = mock() + val firstImage = mock().run { whenever(isInserted).thenReturn(true); this } + whenever(firstAlbum.images).thenReturn(listOf(firstImage)) + val secondAlbum = mock() + val secondImage = mock().run { whenever(isInserted).thenReturn(true); this } + whenever(secondAlbum.images).thenReturn(listOf(secondImage)) + whenever(firstAlbum.title).thenReturn(firstAlbumTitle) + whenever(secondAlbum.title).thenReturn(secondAlbumTitle) + whenever(rootAlbum.albums).thenReturn(listOf(firstAlbum, secondAlbum)) + whenever(this.rootAlbum).thenReturn(rootAlbum) + } + } + + @Test + fun `requesting nothing will show the albums of the current sone`() { + request("", GET) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["soneRequested"], equalTo(true)) + assertThat(templateContext["sone"], equalTo(currentSone)) + } + + @Test + fun `page is link-excepted`() { + assertThat(page.isLinkExcepted(null), equalTo(true)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt new file mode 100644 index 0000000..04c83d6 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt @@ -0,0 +1,82 @@ +package net.pterodactylus.sone.web.pages + +import com.google.common.base.Optional.fromNullable +import com.google.common.base.Predicate +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.notify.PostVisibilityFilter +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.IndexPage +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.util.web.Method.GET +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers + +/** + * Unit test for [IndexPage]. + */ +class IndexPageTest : WebPageTest() { + + private val postVisibilityFilter = mock() + private val page = IndexPage(template, webInterface, postVisibilityFilter) + + @Before + fun setupPostVisibilityFilter() { + whenever(postVisibilityFilter.isVisible(ArgumentMatchers.eq(currentSone))).thenReturn(object : Predicate { + override fun apply(input: Post?) = true + }) + } + + private fun createPost(time: Long, directed: Boolean = false) = mock().apply { + whenever(this.time).thenReturn(time) + whenever(recipient).thenReturn(fromNullable(if (directed) currentSone else null)) + } + + @Test + fun `index page shows all posts of current sone`() { + val posts = listOf(createPost(3000), createPost(2000), createPost(1000)) + whenever(currentSone.posts).thenReturn(posts) + request("", GET) + page.handleRequest(freenetRequest, templateContext) + @Suppress("UNCHECKED_CAST") + assertThat(templateContext["posts"] as Iterable, contains(*posts.toTypedArray())) + } + + @Test + fun `index page shows posts directed at current sone from non-followed sones`() { + val posts = listOf(createPost(3000), createPost(2000), createPost(1000)) + whenever(currentSone.posts).thenReturn(posts) + val notFollowedSone = mock() + val notFollowedPosts = listOf(createPost(2500, true), createPost(1500)) + whenever(notFollowedSone.posts).thenReturn(notFollowedPosts) + addSone("notfollowed1", notFollowedSone) + request("", GET) + page.handleRequest(freenetRequest, templateContext) + @Suppress("UNCHECKED_CAST") + assertThat(templateContext["posts"] as Iterable, contains( + posts[0], notFollowedPosts[0], posts[1], posts[2] + )) + } + + @Test + fun `index page does not show duplicate posts`() { + val posts = listOf(createPost(3000), createPost(2000), createPost(1000)) + whenever(currentSone.posts).thenReturn(posts) + val followedSone = mock() + val followedPosts = listOf(createPost(2500, true), createPost(1500)) + whenever(followedSone.posts).thenReturn(followedPosts) + whenever(currentSone.friends).thenReturn(listOf("followed1", "followed2")) + addSone("followed1", followedSone) + request("", GET) + page.handleRequest(freenetRequest, templateContext) + @Suppress("UNCHECKED_CAST") + assertThat(templateContext["posts"] as Iterable, contains( + posts[0], followedPosts[0], posts[1], followedPosts[1], posts[2] + )) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt new file mode 100644 index 0000000..0476c50 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt @@ -0,0 +1,184 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Image +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.PostReply +import net.pterodactylus.sone.data.Profile +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.freenet.wot.Identity +import net.pterodactylus.sone.freenet.wot.OwnIdentity +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.KnownSonesPage +import net.pterodactylus.sone.web.pages.WebPageTest +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.junit.Before +import org.junit.Test + +/** + * Unit test for [KnownSonesPage]. + */ +class KnownSonesPageTest : WebPageTest() { + + private val page = KnownSonesPage(template, webInterface) + + private val sones = listOf( + createSone(1000, 4, 7, 2, "sone2", true, true), + createSone(2000, 3, 2, 3, "Sone1", false, true), + createSone(3000, 3, 8, 1, "Sone3", true, false), + createSone(4000, 1, 6, 0, "sone0", false, false) + ) + + @Before + fun setupSones() { + addSone("sone1", sones[0]) + addSone("sone2", sones[1]) + addSone("sone3", sones[2]) + addSone("sone4", sones[3]) + } + + private fun createSone(time: Long, posts: Int, replies: Int, images: Int, name: String, local: Boolean, new: Boolean) = mock().apply { + whenever(identity).thenReturn(if (local) mock() else mock()) + whenever(isKnown).thenReturn(!new) + whenever(this.time).thenReturn(time) + whenever(this.posts).thenReturn((0..(posts - 1)).map { mock() }) + whenever(this.replies).thenReturn((0..(replies - 1)).map { mock() }.toSet()) + val album = mock() + whenever(album.images).thenReturn(((0..(images - 1)).map { mock() })) + val rootAlbum = mock().apply { + whenever(albums).thenReturn(listOf(album)) + } + whenever(this.rootAlbum).thenReturn(rootAlbum) + whenever(this.profile).thenReturn(mock()) + whenever(id).thenReturn(name.toLowerCase()) + whenever(this.name).thenReturn(name) + } + + private fun verifySonesAreInOrder(vararg indices: Int) { + @Suppress("UNCHECKED_CAST") + assertThat(templateContext["knownSones"] as Iterable, contains( + *indices.map { sones[it] }.toTypedArray() + )) + } + + @Test + fun `default known sones are sorted newest first`() { + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 2, 1, 0) + } + + @Test + fun `known sones can be sorted by oldest first`() { + addHttpRequestParameter("order", "asc") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(0, 1, 2, 3) + } + + @Test + fun `known sones can be sorted by posts, most posts first`() { + addHttpRequestParameter("sort", "posts") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(0, 2, 1, 3) + } + + @Test + fun `known sones can be sorted by posts, least posts first`() { + addHttpRequestParameter("sort", "posts") + addHttpRequestParameter("order", "asc") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 1, 2, 0) + } + + @Test + fun `known sones can be sorted by images, most images first`() { + addHttpRequestParameter("sort", "images") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(1, 0, 2, 3) + } + + @Test + fun `known sones can be sorted by images, least images first`() { + addHttpRequestParameter("sort", "images") + addHttpRequestParameter("order", "asc") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 2, 0, 1) + } + + @Test + fun `known sones can be sorted by nice name, ascending`() { + addHttpRequestParameter("sort", "name") + addHttpRequestParameter("order", "asc") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 1, 0, 2) + } + + @Test + fun `known sones can be sorted by nice name, descending`() { + addHttpRequestParameter("sort", "name") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(2, 0, 1, 3) + } + + @Test + fun `known sones can be filtered by local sones`() { + addHttpRequestParameter("filter", "own") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(2, 0) + } + + @Test + fun `known sones can be filtered by non-local sones`() { + addHttpRequestParameter("filter", "not-own") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 1) + } + + @Test + fun `known sones can be filtered by new sones`() { + addHttpRequestParameter("filter", "new") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(1, 0) + } + + @Test + fun `known sones can be filtered by known sones`() { + addHttpRequestParameter("filter", "not-new") + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 2) + } + + @Test + fun `known sones can be filtered by followed sones`() { + addHttpRequestParameter("filter", "followed") + listOf("sone1", "sone3").forEach { whenever(currentSone.hasFriend(it)).thenReturn(true) } + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(2, 1) + } + + @Test + fun `known sones can be filtered by not-followed sones`() { + addHttpRequestParameter("filter", "not-followed") + listOf("sone1", "sone3").forEach { whenever(currentSone.hasFriend(it)).thenReturn(true) } + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 0) + } + + @Test + fun `known sones can not be filtered by followed sones if there is no current sone`() { + addHttpRequestParameter("filter", "followed") + unsetCurrentSone() + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 2, 1, 0) + } + + @Test + fun `known sones can not be filtered by not-followed sones if there is no current sone`() { + addHttpRequestParameter("filter", "not-followed") + unsetCurrentSone() + page.handleRequest(freenetRequest, templateContext) + verifySonesAreInOrder(3, 2, 1, 0) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/LikePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/LikePageTest.kt new file mode 100644 index 0000000..10baf5e --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/LikePageTest.kt @@ -0,0 +1,58 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.web.pages.LikePage +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.junit.Test +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions + +/** + * Unit test for [LikePage]. + */ +class LikePageTest : WebPageTest() { + + private val page = LikePage(template, webInterface) + + override fun getPage() = page + + @Test + fun `get request does not redirect`() { + request("", GET) + page.handleRequest(freenetRequest, templateContext) + } + + @Test + fun `post request with post id likes post and redirects to return page`() { + request("", POST) + addHttpRequestParameter("type", "post") + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(currentSone).addLikedPostId("post-id") + } + } + + @Test + fun `post request with reply id likes post and redirects to return page`() { + request("", POST) + addHttpRequestParameter("type", "reply") + addHttpRequestParameter("reply", "reply-id") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(currentSone).addLikedReplyId("reply-id") + } + } + + @Test + fun `post request with invalid likes redirects to return page`() { + request("", POST) + addHttpRequestParameter("type", "foo") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verifyNoMoreInteractions(currentSone) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/LockSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/LockSonePageTest.kt new file mode 100644 index 0000000..9a6876c --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/LockSonePageTest.kt @@ -0,0 +1,40 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.web.pages.LockSonePage +import net.pterodactylus.sone.web.pages.WebPageTest +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [LockSonePage]. + */ +class LockSonePageTest : WebPageTest() { + + private val page = LockSonePage(template, webInterface) + + override fun getPage() = page + + @Test + fun `locking an invalid local sone redirects to return page`() { + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(core, never()).lockSone(any()) + } + } + + @Test + fun `locking an valid local sone locks the sone and redirects to return page`() { + addHttpRequestParameter("sone", "sone-id") + val sone = mock() + addLocalSone("sone-id", sone) + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(core).lockSone(sone) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/LoginPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/LoginPageTest.kt new file mode 100644 index 0000000..0da820c --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/LoginPageTest.kt @@ -0,0 +1,150 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.freenet.wot.Identity +import net.pterodactylus.sone.freenet.wot.OwnIdentity +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.thenReturnMock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.WebPageTest +import net.pterodactylus.sone.web.pages.LoginPage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.containsInAnyOrder +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.nullValue +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [LoginPage]. + */ +class LoginPageTest : WebPageTest() { + + private val page = LoginPage(template, webInterface) + + private val sones = listOf(createSone("Sone", "Test"), createSone("Test"), createSone("Sone")) + + override fun getPage() = page + + private fun createSone(vararg contexts: String) = mock().apply { + whenever(id).thenReturn(hashCode().toString()) + val identity = mock().apply { + whenever(this.contexts).thenReturn(contexts.toSet()) + contexts.forEach { whenever(hasContext(it)).thenReturn(true) } + } + whenever(this.identity).thenReturn(identity) + whenever(profile).thenReturnMock() + } + + @Before + fun setupSones() { + addLocalSone("sone1", sones[0]) + addLocalSone("sone2", sones[1]) + addLocalSone("sone3", sones[2]) + addOwnIdentity(sones[0].identity as OwnIdentity) + addOwnIdentity(sones[1].identity as OwnIdentity) + addOwnIdentity(sones[2].identity as OwnIdentity) + } + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("login.html")) + } + + @Test + fun `page does not require login`() { + assertThat(page.requiresLogin(), equalTo(false)) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `get request stores sones in template context`() { + request("", GET) + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["sones"] as Iterable, containsInAnyOrder(sones[0], sones[1], sones[2])) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `get request stores identities without sones in template context`() { + request("", GET) + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["identitiesWithoutSone"] as Iterable, contains(sones[1].identity)) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `post request with invalid sone sets sones and identities without sone in template context`() { + request("", POST) + page.processTemplate(freenetRequest, templateContext) + assertThat(templateContext["sones"] as Iterable, containsInAnyOrder(sones[0], sones[1], sones[2])) + assertThat(templateContext["identitiesWithoutSone"] as Iterable, contains(sones[1].identity)) + } + + @Test + fun `post request with valid sone logs in the sone and redirects to index page`() { + request("", POST) + addHttpRequestParameter("sone-id", "sone2") + verifyRedirect("index.html") { + verify(webInterface).setCurrentSone(toadletContext, sones[1]) + } + } + + @Test + fun `post request with valid sone and target redirects to target page`() { + request("", POST) + addHttpRequestParameter("sone-id", "sone2") + addHttpRequestParameter("target", "foo.html") + verifyRedirect("foo.html") { + verify(webInterface).setCurrentSone(toadletContext, sones[1]) + } + } + + @Test + fun `redirect to index html if a sone is logged in`() { + assertThat(page.getRedirectTarget(freenetRequest), equalTo("index.html")) + } + + @Test + fun `do not redirect if no sone is logged in`() { + unsetCurrentSone() + assertThat(page.getRedirectTarget(freenetRequest), nullValue()) + } + + @Test + fun `page is not enabled if full access required and request is not full access`() { + core.preferences.isRequireFullAccess = true + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `page is enabled if no full access is required and there is no current sone`() { + unsetCurrentSone() + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + + @Test + fun `page is not enabled if no full access is required but there is a current sone`() { + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `page is enabled if full access required and request is full access and there is no current sone`() { + core.preferences.isRequireFullAccess = true + unsetCurrentSone() + whenever(toadletContext.isAllowedFullAccess).thenReturn(true) + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + + @Test + fun `page is not enabled if full access required and request is full access but there is a current sone`() { + core.preferences.isRequireFullAccess = true + whenever(toadletContext.isAllowedFullAccess).thenReturn(true) + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/LogoutPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/LogoutPageTest.kt new file mode 100644 index 0000000..14ba030 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/LogoutPageTest.kt @@ -0,0 +1,59 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.LogoutPage +import net.pterodactylus.sone.web.pages.WebPageTest +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [LogoutPage]. + */ +class LogoutPageTest : WebPageTest() { + + private val page = LogoutPage(template, webInterface) + + override fun getPage() = page + + @Test + fun `page unsets current sone and redirects to index`() { + verifyRedirect("index.html") { + verify(webInterface).setCurrentSone(toadletContext, null) + } + } + + @Test + fun `page is not enabled if sone requires full access and request does not have full access`() { + core.preferences.isRequireFullAccess = true + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `page is disabled if no sone is logged in`() { + unsetCurrentSone() + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `page is disabled if sone is logged in but there is only one sone`() { + whenever(core.localSones).thenReturn(listOf(currentSone)) + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `page is enabled if sone is logged in and there is more than one sone`() { + whenever(core.localSones).thenReturn(listOf(currentSone, currentSone)) + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + + @Test + fun `page is enabled if full access is required and present and sone is logged in and there is more than one sone`() { + core.preferences.isRequireFullAccess = true + whenever(toadletContext.isAllowedFullAccess).thenReturn(true) + whenever(core.localSones).thenReturn(listOf(currentSone, currentSone)) + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/MarkAsKnownPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/MarkAsKnownPageTest.kt new file mode 100644 index 0000000..cc19b60 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/MarkAsKnownPageTest.kt @@ -0,0 +1,69 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.PostReply +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.web.pages.MarkAsKnownPage +import net.pterodactylus.sone.web.pages.WebPageTest +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [MarkAsKnownPage]. + */ +class MarkAsKnownPageTest : WebPageTest() { + + private val page = MarkAsKnownPage(template, webInterface) + + override fun getPage() = page + + @Test + fun `posts can be marked as known`() { + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("type", "post") + addHttpRequestParameter("id", "post1 post2 post3") + val posts = listOf(mock(), mock()) + addPost("post1", posts[0]) + addPost("post3", posts[1]) + verifyRedirect("return.html") { + verify(core).markPostKnown(posts[0]) + verify(core).markPostKnown(posts[1]) + } + } + + @Test + fun `replies can be marked as known`() { + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("type", "reply") + addHttpRequestParameter("id", "reply1 reply2 reply3") + val replies = listOf(mock(), mock()) + addPostReply("reply1", replies[0]) + addPostReply("reply3", replies[1]) + verifyRedirect("return.html") { + verify(core).markReplyKnown(replies[0]) + verify(core).markReplyKnown(replies[1]) + } + } + + @Test + fun `sones can be marked as known`() { + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("type", "sone") + addHttpRequestParameter("id", "sone1 sone2 sone3") + val sones = listOf(mock(), mock()) + addSone("sone1", sones[0]) + addSone("sone3", sones[1]) + verifyRedirect("return.html") { + verify(core).markSoneKnown(sones[0]) + verify(core).markSoneKnown(sones[1]) + } + } + + @Test + fun `different type redirects to invalid page`() { + addHttpRequestParameter("type", "foo") + verifyRedirect("invalid.html") + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt new file mode 100644 index 0000000..950a3fe --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/OptionsPageTest.kt @@ -0,0 +1,343 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions +import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.FOLLOWED +import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.TRUSTED +import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired +import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS +import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO +import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.OptionsPage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.hasItem +import org.hamcrest.Matchers.nullValue +import org.junit.Before +import org.junit.Test + +/** + * Unit test for [OptionsPage]. + */ +class OptionsPageTest : WebPageTest() { + + private val page = OptionsPage(template, webInterface) + + override fun getPage() = page + + @Before + fun setupPreferences() { + core.preferences.insertionDelay = 1 + core.preferences.charactersPerPost = 50 + core.preferences.fcpFullAccessRequired = WRITING + core.preferences.imagesPerPage = 4 + core.preferences.isFcpInterfaceActive = true + core.preferences.isRequireFullAccess = true + core.preferences.negativeTrust = 7 + core.preferences.positiveTrust = 8 + core.preferences.postCutOffLength = 51 + core.preferences.postsPerPage = 10 + core.preferences.trustComment = "11" + } + + @Before + fun setupSoneOptions() { + whenever(currentSone.options).thenReturn(DefaultSoneOptions().apply { + isAutoFollow = true + isShowNewPostNotifications = true + isShowNewReplyNotifications = true + isShowNewSoneNotifications = true + isSoneInsertNotificationEnabled = true + loadLinkedImages = FOLLOWED + showCustomAvatars = FOLLOWED + }) + } + + @Test + fun `get request stores all preferences in the template context`() { + request("", GET) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["auto-follow"], equalTo(true)) + assertThat(templateContext["show-notification-new-sones"], equalTo(true)) + assertThat(templateContext["show-notification-new-posts"], equalTo(true)) + assertThat(templateContext["show-notification-new-replies"], equalTo(true)) + assertThat(templateContext["enable-sone-insert-notifications"], equalTo(true)) + assertThat(templateContext["load-linked-images"], equalTo("FOLLOWED")) + assertThat(templateContext["show-custom-avatars"], equalTo("FOLLOWED")) + assertThat(templateContext["insertion-delay"], equalTo(1)) + assertThat(templateContext["characters-per-post"], equalTo(50)) + assertThat(templateContext["fcp-full-access-required"], equalTo(1)) + assertThat(templateContext["images-per-page"], equalTo(4)) + assertThat(templateContext["fcp-interface-active"], equalTo(true)) + assertThat(templateContext["require-full-access"], equalTo(true)) + assertThat(templateContext["negative-trust"], equalTo(7)) + assertThat(templateContext["positive-trust"], equalTo(8)) + assertThat(templateContext["post-cut-off-length"], equalTo(51)) + assertThat(templateContext["posts-per-page"], equalTo(10)) + assertThat(templateContext["trust-comment"], equalTo("11")) + } + + @Test + fun `get request without sone does not store sone-specific preferences in the template context`() { + request("", GET) + unsetCurrentSone() + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["auto-follow"], nullValue()) + assertThat(templateContext["show-notification-new-sones"], nullValue()) + assertThat(templateContext["show-notification-new-posts"], nullValue()) + assertThat(templateContext["show-notification-new-replies"], nullValue()) + assertThat(templateContext["enable-sone-insert-notifications"], nullValue()) + assertThat(templateContext["load-linked-images"], nullValue()) + assertThat(templateContext["show-custom-avatars"], nullValue()) + } + + private fun verifyThatOptionCanBeSet(option: String, setValue: Any?, expectedValue: T, getter: () -> T) { + request("", POST) + addHttpRequestParameter(option, setValue.toString()) + addHttpRequestParameter("show-custom-avatars", "ALWAYS") + addHttpRequestParameter("load-linked-images", "ALWAYS") + verifyRedirect("options.html") { + assertThat(getter(), equalTo(expectedValue)) + } + } + + @Test + fun `auto-follow option can be set`() { + verifyThatOptionCanBeSet("auto-follow", "checked", true) { currentSone.options.isAutoFollow } + } + + @Test + fun `show new sone notification option can be set`() { + verifyThatOptionCanBeSet("show-notification-new-sones", "checked", true) { currentSone.options.isShowNewSoneNotifications } + } + + @Test + fun `show new post notification option can be set`() { + verifyThatOptionCanBeSet("show-notification-new-posts", "checked", true) { currentSone.options.isShowNewPostNotifications } + } + + @Test + fun `show new reply notification option can be set`() { + verifyThatOptionCanBeSet("show-notification-new-replies", "checked", true) { currentSone.options.isShowNewReplyNotifications } + } + + @Test + fun `enable sone insert notifications option can be set`() { + verifyThatOptionCanBeSet("enable-sone-insert-notifications", "checked", true) { currentSone.options.isSoneInsertNotificationEnabled } + } + + @Test + fun `load linked images option can be set`() { + verifyThatOptionCanBeSet("load-linked-images", "TRUSTED", TRUSTED) { currentSone.options.loadLinkedImages } + } + + @Test + fun `show custom avatar option can be set`() { + verifyThatOptionCanBeSet("show-custom-avatars", "TRUSTED", TRUSTED) { currentSone.options.showCustomAvatars } + } + + private fun verifyThatWrongValueForPreferenceIsDetected(name: String, value: String) { + unsetCurrentSone() + request("", POST) + addHttpRequestParameter(name, value) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["fieldErrors"] as Iterable<*>, hasItem(name)) + } + + private fun verifyThatPreferencesCanBeSet(name: String, setValue: String?, expectedValue: T, getter: () -> T) { + unsetCurrentSone() + request("", POST) + addHttpRequestParameter(name, setValue) + verifyRedirect("options.html") { + assertThat(getter(), equalTo(expectedValue)) + } + } + + @Test + fun `insertion delay can not be set to less than 0 seconds`() { + verifyThatWrongValueForPreferenceIsDetected("insertion-delay", "-1") + } + + @Test + fun `insertion delay can be set to 0 seconds`() { + verifyThatPreferencesCanBeSet("insertion-delay", "0", 0) { core.preferences.insertionDelay } + } + + @Test + fun `setting insertion to an invalid value will reset it`() { + verifyThatPreferencesCanBeSet("insertion-delay", "foo", 60) { core.preferences.insertionDelay } + } + + @Test + fun `characters per post can not be set to less than -1`() { + verifyThatWrongValueForPreferenceIsDetected("characters-per-post", "-2") + } + + @Test + fun `characters per post can be set to -1`() { + verifyThatPreferencesCanBeSet("characters-per-post", "-1", -1) { core.preferences.charactersPerPost } + } + + @Test + fun `characters per post can not be set to 0`() { + verifyThatWrongValueForPreferenceIsDetected("characters-per-post", "0") + } + + @Test + fun `characters per post can not be set to 49`() { + verifyThatWrongValueForPreferenceIsDetected("characters-per-post", "49") + } + + @Test + fun `characters per post can be set to 50`() { + verifyThatPreferencesCanBeSet("characters-per-post", "50", 50) { core.preferences.charactersPerPost } + } + + @Test + fun `fcp full acess required option can be set to always`() { + verifyThatPreferencesCanBeSet("fcp-full-access-required", "2", ALWAYS) { core.preferences.fcpFullAccessRequired } + } + + @Test + fun `fcp full acess required option can be set to writing`() { + verifyThatPreferencesCanBeSet("fcp-full-access-required", "1", WRITING) { core.preferences.fcpFullAccessRequired } + } + + @Test + fun `fcp full acess required option can be set to no`() { + verifyThatPreferencesCanBeSet("fcp-full-access-required", "0", NO) { core.preferences.fcpFullAccessRequired } + } + + @Test + fun `fcp full acess required option is not changed if invalid value is set`() { + verifyThatPreferencesCanBeSet("fcp-full-access-required", "foo", WRITING) { core.preferences.fcpFullAccessRequired } + } + + @Test + fun `images per page can not be set to 0`() { + verifyThatWrongValueForPreferenceIsDetected("images-per-page", "0") + } + + @Test + fun `images per page can be set to 1`() { + verifyThatPreferencesCanBeSet("images-per-page", "1", 1) { core.preferences.imagesPerPage } + } + + @Test + fun `images per page is set to 9 if invalid value is requested`() { + verifyThatPreferencesCanBeSet("images-per-page", "foo", 9) { core.preferences.imagesPerPage } + } + + @Test + fun `fcp interface can be set to true`() { + verifyThatPreferencesCanBeSet("fcp-interface-active", "checked", true) { core.preferences.isFcpInterfaceActive } + } + + @Test + fun `fcp interface can be set to false`() { + verifyThatPreferencesCanBeSet("fcp-interface-active", null, false) { core.preferences.isFcpInterfaceActive } + } + + @Test + fun `require full access can be set to true`() { + verifyThatPreferencesCanBeSet("require-full-access", "checked", true) { core.preferences.isRequireFullAccess } + } + + @Test + fun `require full access can be set to false`() { + verifyThatPreferencesCanBeSet("require-full-access", null, false) { core.preferences.isRequireFullAccess } + } + + @Test + fun `negative trust can not be set to -101`() { + verifyThatWrongValueForPreferenceIsDetected("negative-trust", "-101") + } + + @Test + fun `negative trust can be set to -100`() { + verifyThatPreferencesCanBeSet("negative-trust", "-100", -100) { core.preferences.negativeTrust } + } + + @Test + fun `negative trust can be set to 100`() { + verifyThatPreferencesCanBeSet("negative-trust", "100", 100) { core.preferences.negativeTrust } + } + + @Test + fun `negative trust can not be set to 101`() { + verifyThatWrongValueForPreferenceIsDetected("negative-trust", "101") + } + + @Test + fun `negative trust is set to default on invalid value`() { + verifyThatPreferencesCanBeSet("negative-trust", "invalid", -25) { core.preferences.negativeTrust } + } + + @Test + fun `positive trust can not be set to -1`() { + verifyThatWrongValueForPreferenceIsDetected("positive-trust", "-1") + } + + @Test + fun `positive trust can be set to 0`() { + verifyThatPreferencesCanBeSet("positive-trust", "0", 0) { core.preferences.positiveTrust } + } + + @Test + fun `positive trust can be set to 100`() { + verifyThatPreferencesCanBeSet("positive-trust", "100", 100) { core.preferences.positiveTrust } + } + + @Test + fun `positive trust can not be set to 101`() { + verifyThatWrongValueForPreferenceIsDetected("positive-trust", "101") + } + + @Test + fun `positive trust is set to default on invalid value`() { + verifyThatPreferencesCanBeSet("positive-trust", "invalid", 75) { core.preferences.positiveTrust } + } + + @Test + fun `post cut off length can not be set to -49`() { + verifyThatWrongValueForPreferenceIsDetected("post-cut-off-length", "-49") + } + + @Test + fun `post cut off length can be set to 50`() { + verifyThatPreferencesCanBeSet("post-cut-off-length", "50", 50) { core.preferences.postCutOffLength } + } + + @Test + fun `post cut off length is set to default on invalid value`() { + verifyThatPreferencesCanBeSet("post-cut-off-length", "invalid", 200) { core.preferences.postCutOffLength } + } + + @Test + fun `posts per page can not be set to 0`() { + verifyThatWrongValueForPreferenceIsDetected("posts-per-page", "-49") + } + + @Test + fun `posts per page can be set to 1`() { + verifyThatPreferencesCanBeSet("posts-per-page", "1", 1) { core.preferences.postsPerPage } + } + + @Test + fun `posts per page is set to default on invalid value`() { + verifyThatPreferencesCanBeSet("posts-per-page", "invalid", 10) { core.preferences.postsPerPage } + } + + @Test + fun `trust comment can be set`() { + verifyThatPreferencesCanBeSet("trust-comment", "trust", "trust") { core.preferences.trustComment } + } + + @Test + fun `trust comment is set to default when set to empty value`() { + verifyThatPreferencesCanBeSet("trust-comment", "", "Set from Sone Web Interface") { core.preferences.trustComment } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/ReloadingPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/ReloadingPageTest.kt new file mode 100644 index 0000000..2d44010 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/ReloadingPageTest.kt @@ -0,0 +1,51 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.web.pages.ReloadingPage +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.web.Method.GET +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.text.Charsets.UTF_8 + +/** + * Unit test for [ReloadingPage]. + */ +class ReloadingPageTest : WebPageTest() { + + @Rule @JvmField val tempFolder = TemporaryFolder() + private val folder by lazy { tempFolder.newFolder() } + private val page by lazy { ReloadingPage("/prefix/", folder.path, "text/plain") } + + @Test + fun `page returns correct path prefix`() { + assertThat(page.path, equalTo("/prefix/")) + } + + @Test + fun `page returns that it’s a prefix page`() { + assertThat(page.isPrefixPage, equalTo(true)) + } + + @Test + fun `requesting invalid file results in 404`() { + request("/prefix/path/file.txt", GET) + page.handleRequest(freenetRequest, response) + assertThat(response.statusCode, equalTo(404)) + } + + @Test + fun `requesting valid file results in 200 and delivers file`() { + Files.write(Paths.get(folder.path, "file.txt"), listOf("Hello", "World"), UTF_8) + request("/prefix/path/file.txt", GET) + page.handleRequest(freenetRequest, response) + assertThat(response.statusCode, equalTo(200)) + assertThat(response.contentType, equalTo("text/plain")) + assertThat(responseBytes, equalTo("Hello\nWorld\n".toByteArray())) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt new file mode 100644 index 0000000..db6903e --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt @@ -0,0 +1,67 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.core.SoneRescuer +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.RescuePage +import net.pterodactylus.util.web.Method.GET +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [RescuePage]. + */ +class RescuePageTest : WebPageTest() { + + private val page = RescuePage(template, webInterface) + + private val soneRescuer = mock() + + override fun getPage() = page + + @Before + fun setupSoneRescuer() { + whenever(core.getSoneRescuer(currentSone)).thenReturn(soneRescuer) + } + + @Test + fun `get request sets rescuer in template context`() { + request("", GET) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["soneRescuer"], equalTo(soneRescuer)) + } + + @Test + fun `post request redirects to rescue page`() { + request("", POST) + verifyRedirect("rescue.html") + } + + @Test + fun `post request with fetch and invalid edition starts next fetch`() { + request("", POST) + addHttpRequestParameter("fetch", "true") + verifyRedirect("rescue.html") { + verify(soneRescuer, never()).setEdition(anyLong()) + verify(soneRescuer).startNextFetch() + } + } + + @Test + fun `post request with fetch and valid edition sets edition and starts next fetch`() { + request("", POST) + addHttpRequestParameter("fetch", "true") + addHttpRequestParameter("edition", "123") + verifyRedirect("rescue.html") { + verify(soneRescuer).setEdition(123L) + verify(soneRescuer).startNextFetch() + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/SearchPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/SearchPageTest.kt new file mode 100644 index 0000000..65b65a9 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/SearchPageTest.kt @@ -0,0 +1,257 @@ +package net.pterodactylus.sone.web.pages + +import com.google.common.base.Optional.absent +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Image +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.PostReply +import net.pterodactylus.sone.data.Profile +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.asOptional +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.SearchPage +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.junit.Test + +/** + * Unit test for [SearchPage]. + */ +class SearchPageTest : WebPageTest() { + + private val page = SearchPage(template, webInterface) + + override fun getPage() = page + + @Test + fun `empty query redirects to index page`() { + verifyRedirect("index.html") + } + + @Test + fun `empty search phrases redirect to index page`() { + addHttpRequestParameter("query", "\"\"") + verifyRedirect("index.html") + } + + @Test + fun `invalid search phrases redirect to index page`() { + addHttpRequestParameter("query", "\"") + verifyRedirect("index.html") + } + + @Test + fun `searching for sone link redirects to view sone page`() { + addSone("sone-id", mock()) + addHttpRequestParameter("query", "sone://sone-id") + verifyRedirect("viewSone.html?sone=sone-id") + } + + @Test + fun `searching for sone link without prefix redirects to view sone page`() { + addSone("sone-id", mock()) + addHttpRequestParameter("query", "sone-id") + verifyRedirect("viewSone.html?sone=sone-id") + } + + @Test + fun `searching for a post link redirects to post page`() { + addPost("post-id", mock()) + addHttpRequestParameter("query", "post://post-id") + verifyRedirect("viewPost.html?post=post-id") + } + + @Test + fun `searching for a post ID without prefix redirects to post page`() { + addPost("post-id", mock()) + addHttpRequestParameter("query", "post-id") + verifyRedirect("viewPost.html?post=post-id") + } + + @Test + fun `searching for a reply link redirects to the post page`() { + val postReply = mock().apply { whenever(postId).thenReturn("post-id") } + addPostReply("reply-id", postReply) + addHttpRequestParameter("query", "reply://reply-id") + verifyRedirect("viewPost.html?post=post-id") + } + + @Test + fun `searching for a reply ID redirects to the post page`() { + val postReply = mock().apply { whenever(postId).thenReturn("post-id") } + addPostReply("reply-id", postReply) + addHttpRequestParameter("query", "reply-id") + verifyRedirect("viewPost.html?post=post-id") + } + + @Test + fun `searching for an album link redirects to the image browser`() { + addAlbum("album-id", mock()) + addHttpRequestParameter("query", "album://album-id") + verifyRedirect("imageBrowser.html?album=album-id") + } + + @Test + fun `searching for an album ID redirects to the image browser`() { + addAlbum("album-id", mock()) + addHttpRequestParameter("query", "album-id") + verifyRedirect("imageBrowser.html?album=album-id") + } + + @Test + fun `searching for an image link redirects to the image browser`() { + addImage("image-id", mock()) + addHttpRequestParameter("query", "image://image-id") + verifyRedirect("imageBrowser.html?image=image-id") + } + + @Test + fun `searching for an image ID redirects to the image browser`() { + addImage("image-id", mock()) + addHttpRequestParameter("query", "image-id") + verifyRedirect("imageBrowser.html?image=image-id") + } + + private fun createReply(text: String, postId: String? = null, sone: Sone? = null) = mock().apply { + whenever(this.text).thenReturn(text) + postId?.run { whenever(this@apply.postId).thenReturn(postId) } + sone?.run { whenever(this@apply.sone).thenReturn(sone) } + } + + private fun createPost(id: String, text: String) = mock().apply { + whenever(this.id).thenReturn(id) + whenever(recipient).thenReturn(absent()) + whenever(this.text).thenReturn(text) + } + + private fun createSoneWithPost(post: Post, sone: Sone? = null) = sone?.apply { + whenever(posts).thenReturn(listOf(post)) + } ?: mock().apply { + whenever(posts).thenReturn(listOf(post)) + whenever(profile).thenReturn(Profile(this)) + } + + @Test + fun `searching for a single word finds the post`() { + val postWithMatch = createPost("post-with-match", "the word here") + val postWithoutMatch = createPost("post-without-match", "no match here") + val soneWithMatch = createSoneWithPost(postWithMatch) + val soneWithoutMatch = createSoneWithPost(postWithoutMatch) + addSone("sone-with-match", soneWithMatch) + addSone("sone-without-match", soneWithoutMatch) + addHttpRequestParameter("query", "word") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["postHits"], contains(postWithMatch)) + } + + @Test + fun `searching for a single word locates word in reply`() { + val postWithMatch = createPost("post-with-match", "no match here") + val postWithoutMatch = createPost("post-without-match", "no match here") + val soneWithMatch = createSoneWithPost(postWithMatch) + val soneWithoutMatch = createSoneWithPost(postWithoutMatch) + val replyWithMatch = createReply("the word here", "post-with-match", soneWithMatch) + val replyWithoutMatch = createReply("no match here", "post-without-match", soneWithoutMatch) + addPostReply("reply-with-match", replyWithMatch) + addPostReply("reply-without-match", replyWithoutMatch) + addSone("sone-with-match", soneWithMatch) + addSone("sone-without-match", soneWithoutMatch) + addHttpRequestParameter("query", "word") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["postHits"], contains(postWithMatch)) + } + + private fun createSoneWithPost(idPostfix: String, text: String, recipient: Sone? = null, sender: Sone? = null) = + createPost("post-$idPostfix", text, recipient).apply { + addSone("sone-$idPostfix", createSoneWithPost(this, sender)) + } + + @Test + fun `earlier matches score higher than later matches`() { + val postWithEarlyMatch = createSoneWithPost("with-early-match", "optional match") + val postWithLaterMatch = createSoneWithPost("with-later-match", "match that is optional") + addHttpRequestParameter("query", "optional ") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["postHits"], contains(postWithEarlyMatch, postWithLaterMatch)) + } + + @Test + fun `searching for required word does not return posts without that word`() { + val postWithRequiredMatch = createSoneWithPost("with-required-match", "required match") + createPost("without-required-match", "not a match") + addHttpRequestParameter("query", "+required ") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["postHits"], contains(postWithRequiredMatch)) + } + + @Test + fun `searching for forbidden word does not return posts with that word`() { + createSoneWithPost("with-forbidden-match", "forbidden match") + val postWithoutForbiddenMatch = createSoneWithPost("without-forbidden-match", "not a match") + addHttpRequestParameter("query", "match -forbidden") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["postHits"], contains(postWithoutForbiddenMatch)) + } + + @Test + fun `searching for a plus sign searches for optional plus sign`() { + val postWithMatch = createSoneWithPost("with-match", "with + match") + createSoneWithPost("without-match", "without match") + addHttpRequestParameter("query", "+") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["postHits"], contains(postWithMatch)) + } + + @Test + fun `searching for a minus sign searches for optional minus sign`() { + val postWithMatch = createSoneWithPost("with-match", "with - match") + createSoneWithPost("without-match", "without match") + addHttpRequestParameter("query", "-") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["postHits"], contains(postWithMatch)) + } + + private fun createPost(id: String, text: String, recipient: Sone?) = mock().apply { + whenever(this.id).thenReturn(id) + val recipientId = recipient?.id + whenever(this.recipientId).thenReturn(recipientId.asOptional()) + whenever(this.recipient).thenReturn(recipient.asOptional()) + whenever(this.text).thenReturn(text) + } + + private fun createSone(id: String, firstName: String, middleName: String, lastName: String) = mock().apply { + whenever(this.id).thenReturn(id) + whenever(this.name).thenReturn(id) + whenever(this.profile).thenReturn(Profile(this).apply { + this.firstName = firstName + this.middleName = middleName + this.lastName = lastName + }) + } + + @Test + fun `searching for a recipient finds the correct post`() { + val recipient = createSone("recipient", "reci", "pi", "ent") + val postWithMatch = createSoneWithPost("with-match", "test", recipient) + createSoneWithPost("without-match", "no match") + addHttpRequestParameter("query", "recipient") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["postHits"], contains(postWithMatch)) + } + + @Test + fun `searching for a field value finds the correct sone`() { + val soneWithProfileField = createSone("sone", "s", "o", "ne") + soneWithProfileField.profile.addField("field").value = "value" + createSoneWithPost("with-match", "test", sender = soneWithProfileField) + createSoneWithPost("without-match", "no match") + addHttpRequestParameter("query", "value") + page.handleRequest(freenetRequest, templateContext) + assertThat(this["soneHits"], contains(soneWithProfileField)) + } + + @Suppress("UNCHECKED_CAST") + private operator fun get(key: String): T? = templateContext[key] as? T + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt new file mode 100644 index 0000000..42d6676 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt @@ -0,0 +1,269 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.main.SonePlugin +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.SoneTemplatePage +import net.pterodactylus.sone.web.page.FreenetRequest +import net.pterodactylus.util.notify.Notification +import net.pterodactylus.util.template.TemplateContext +import net.pterodactylus.util.version.Version +import net.pterodactylus.util.web.Method.GET +import org.hamcrest.Matcher +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.anyOf +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.containsInAnyOrder +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.nullValue +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [SoneTemplatePage]. + */ +class SoneTemplatePageTest : WebPageTest() { + + private val preferences by lazy { core.preferences!! } + private val page = object : SoneTemplatePage("path.html", template, webInterface, true) {} + + @Test + fun `current sone is retrieved from web interface`() { + assertThat(page.getCurrentSone(toadletContext), equalTo(currentSone)) + } + + @Test + fun `retrieving current sone without creation is forwarded to web interface`() { + mock().let { + whenever(webInterface.getCurrentSoneWithoutCreatingSession(toadletContext)).thenReturn(it) + assertThat(page.getCurrentSoneWithoutCreatingSession(toadletContext), equalTo(it)) + } + } + + @Test + fun `setting the current sone is forwarded to web interface`() { + mock().let { + page.setCurrentSone(toadletContext, it) + verify(webInterface).setCurrentSone(toadletContext, it) + } + } + + @Test + fun `page title is empty string if no page title key was given`() { + SoneTemplatePage("path.html", template, null, webInterface).let { page -> + assertThat(page.getPageTitle(freenetRequest), equalTo("")) + } + } + + @Test + fun `page title is retrieved from l10n if page title key is given`() { + SoneTemplatePage("path.html", template, "page.title", webInterface).let { page -> + whenever(l10n.getString("page.title")).thenReturn("Page Title") + assertThat(page.getPageTitle(freenetRequest), equalTo("Page Title")) + } + } + + @Test + fun `additional link nodes contain open search link`() { + addHttpRequestHeader("Host", "www.example.com") + assertThat(page.getAdditionalLinkNodes(freenetRequest), contains(mapOf( + "rel" to "search", + "type" to "application/opensearchdescription+xml", + "title" to "Sone", + "href" to "http://www.example.com/Sone/OpenSearch.xml" + ))) + } + + @Test + fun `style sheets contains sone CSS file`() { + assertThat(page.styleSheets, contains("css/sone.css")) + } + + @Test + fun `shortcut icon is the sone icon`() { + assertThat(page.shortcutIcon, equalTo("images/icon.png")) + } + + @Test + fun `page requires login if require login was specified in the constructor`() { + SoneTemplatePage("path.html", template, webInterface, true).let { page -> + assertThat(page.requiresLogin(), equalTo(true)) + } + } + + @Test + fun `page does not require login if require login was not specified in the constructor`() { + SoneTemplatePage("path.html", template, webInterface, false).let { page -> + assertThat(page.requiresLogin(), equalTo(false)) + } + } + + private fun verifyVariableIsSet(name: String, value: Any) = verifyVariableMatches(name, equalTo(value)) + + private fun verifyVariableMatches(name: String, matcher: Matcher) { + page.processTemplate(freenetRequest, templateContext) + @Suppress("UNCHECKED_CAST") + assertThat(templateContext[name] as T, matcher) + } + + @Test + fun `preferences are set in template context`() { + verifyVariableIsSet("preferences", preferences) + } + + @Test + fun `current sone is set in template context`() { + verifyVariableIsSet("currentSone", currentSone) + } + + @Test + fun `local sones are set in template context`() { + val localSones = listOf(mock(), mock()) + whenever(core.localSones).thenReturn(localSones) + verifyVariableMatches("localSones", containsInAnyOrder(*localSones.toTypedArray())) + } + + @Test + fun `freenet request is set in template context`() { + verifyVariableIsSet("request", freenetRequest) + } + + @Test + fun `current version is set in template context`() { + verifyVariableIsSet("currentVersion", SonePlugin.getPluginVersion()) + } + + @Test + fun `has latest version is set correctly in template context if true`() { + whenever(core.updateChecker.hasLatestVersion()).thenReturn(true) + verifyVariableIsSet("hasLatestVersion", true) + } + + @Test + fun `has latest version is set correctly in template context if false`() { + whenever(core.updateChecker.hasLatestVersion()).thenReturn(false) + verifyVariableIsSet("hasLatestVersion", false) + } + + @Test + fun `latest edition is set in template context`() { + whenever(core.updateChecker.latestEdition).thenReturn(1234L) + verifyVariableIsSet("latestEdition", 1234L) + } + + @Test + fun `latest version is set in template context`() { + whenever(core.updateChecker.latestVersion).thenReturn(Version(1, 2, 3)) + verifyVariableIsSet("latestVersion", Version(1, 2, 3)) + } + + @Test + fun `latest version time is set in template context`() { + whenever(core.updateChecker.latestVersionDate).thenReturn(12345L) + verifyVariableIsSet("latestVersionTime", 12345L) + } + + private fun createNotification(time: Long) = mock().apply { + whenever(createdTime).thenReturn(time) + } + + @Test + fun `notifications are set in template context`() { + val notifications = listOf(createNotification(3000), createNotification(1000), createNotification(2000)) + whenever(webInterface.getNotifications(currentSone)).thenReturn(notifications) + verifyVariableMatches("notifications", contains(notifications[1], notifications[2], notifications[0])) + } + + @Test + fun `notification hash is set in template context`() { + val notifications = listOf(createNotification(3000), createNotification(1000), createNotification(2000)) + whenever(webInterface.getNotifications(currentSone)).thenReturn(notifications) + verifyVariableIsSet("notificationHash", listOf(notifications[1], notifications[2], notifications[0]).hashCode()) + } + + @Test + fun `handleRequest method is called`() { + var called = false + val page = object : SoneTemplatePage("path.html", template, webInterface, true) { + override fun handleRequest(request: FreenetRequest, templateContext: TemplateContext) { + called = true + } + } + page.processTemplate(freenetRequest, templateContext) + assertThat(called, equalTo(true)) + } + + @Test + fun `redirect does not happen if login is not required`() { + val page = SoneTemplatePage("page.html", template, webInterface, false) + assertThat(page.getRedirectTarget(freenetRequest), nullValue()) + } + + @Test + fun `redirect does not happen if sone is logged in`() { + assertThat(page.getRedirectTarget(freenetRequest), nullValue()) + } + + @Test + fun `redirect does happen if sone is not logged in`() { + unsetCurrentSone() + request("index.html", GET) + assertThat(page.getRedirectTarget(freenetRequest), equalTo("login.html?target=index.html")) + } + + @Test + fun `redirect does happen with parameters encoded correctly if sone is not logged in`() { + unsetCurrentSone() + request("index.html", GET) + addHttpRequestParameter("foo", "b=r") + addHttpRequestParameter("baz", "q&o") + assertThat(page.getRedirectTarget(freenetRequest), anyOf( + equalTo("login.html?target=index.html%3Ffoo%3Db%253Dr%26baz%3Dq%2526o"), + equalTo("login.html?target=index.html%3Fbaz%3Dq%2526o%26foo%3Db%253Dr") + )) + } + + @Test + fun `full access requirement is correctly forwarded from the preferences if false`() { + assertThat(page.isFullAccessOnly, equalTo(false)) + } + + @Test + fun `full access requirement is correctly forwarded from the preferences if true`() { + core.preferences.isRequireFullAccess = true + assertThat(page.isFullAccessOnly, equalTo(true)) + } + + @Test + fun `page is disabled if full access is required but request does not have full access`() { + core.preferences.isRequireFullAccess = true + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `page is disabled if login is required but there is no current sone`() { + unsetCurrentSone() + assertThat(page.isEnabled(toadletContext), equalTo(false)) + } + + @Test + fun `page is enabled if login is required and there is a current sone`() { + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + + @Test + fun `page is enabled if full access is required and request has full access and login is required and there is a current sone`() { + core.preferences.isRequireFullAccess = true + whenever(toadletContext.isAllowedFullAccess).thenReturn(true) + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + + @Test + fun `page is enabled if no full access is required and login is not required`() { + SoneTemplatePage("path.html", template, webInterface, false).let { page -> + assertThat(page.isEnabled(toadletContext), equalTo(true)) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/TrustPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/TrustPageTest.kt new file mode 100644 index 0000000..6947f63 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/TrustPageTest.kt @@ -0,0 +1,49 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.web.pages.TrustPage +import net.pterodactylus.util.web.Method.POST +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [TrustPage]. + */ +class TrustPageTest : WebPageTest() { + + private val page = TrustPage(template, webInterface) + + override fun getPage() = page + + @Test + fun `get method does not redirect`() { + page.handleRequest(freenetRequest, templateContext) + } + + @Test + fun `post request with missing sone redirects to return page`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "sone-id") + verifyRedirect("return.html") { + verify(core, never()).trustSone(eq(currentSone), any()) + } + } + + @Test + fun `post request with existing sone trusts the identity and redirects to return page`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "sone-id") + val sone = mock() + addSone("sone-id", sone) + verifyRedirect("return.html") { + verify(core).trustSone(eq(currentSone), eq(sone)) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/UnbookmarkPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/UnbookmarkPageTest.kt new file mode 100644 index 0000000..b18211f --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/UnbookmarkPageTest.kt @@ -0,0 +1,65 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.UnbookmarkPage +import net.pterodactylus.util.web.Method.POST +import org.junit.Test +import org.mockito.Mockito.any +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [UnbookmarkPage]. + */ +class UnbookmarkPageTest : WebPageTest() { + + private val page = UnbookmarkPage(template, webInterface) + + override fun getPage() = page + + @Test + fun `get request does not redirect`() { + page.handleRequest(freenetRequest, templateContext) + } + + @Test + fun `get request with all-not-loaded parameter unloads all not loaded posts and redirects to bookmarks`() { + addHttpRequestParameter("post", "allNotLoaded") + val loadedPost1 = mock().apply { whenever(isLoaded).thenReturn(true) } + val loadedPost2 = mock().apply { whenever(isLoaded).thenReturn(true) } + val notLoadedPost1 = mock() + val notLoadedPost2 = mock() + whenever(core.bookmarkedPosts).thenReturn(setOf(loadedPost1, loadedPost2, notLoadedPost1, notLoadedPost2)) + verifyRedirect("bookmarks.html") { + verify(core).unbookmarkPost(notLoadedPost1) + verify(core).unbookmarkPost(notLoadedPost2) + verify(core, never()).unbookmarkPost(loadedPost1) + verify(core, never()).unbookmarkPost(loadedPost2) + } + } + + @Test + fun `post request does not unbookmark not-present post but redirects to return page`() { + request("", POST) + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(core, never()).unbookmarkPost(any()) + } + } + + @Test + fun `post request unbookmarks present post and redirects to return page`() { + request("", POST) + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("returnPage", "return.html") + val post = mock().apply { whenever(isLoaded).thenReturn(true) } + addPost("post-id", post) + verifyRedirect("return.html") { + verify(core).unbookmarkPost(post) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/UnfollowSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/UnfollowSonePageTest.kt new file mode 100644 index 0000000..5bcd65f --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/UnfollowSonePageTest.kt @@ -0,0 +1,43 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.web.pages.UnfollowSonePage +import net.pterodactylus.util.web.Method.POST +import org.junit.Test +import org.mockito.Mockito.verify + +/** + * Unit test for [UnfollowSonePage]. + */ +class UnfollowSonePageTest : WebPageTest() { + + private val page = UnfollowSonePage(template, webInterface) + + override fun getPage() = page + + @Test + fun `get request does not redirect`() { + page.handleRequest(freenetRequest, templateContext) + } + + @Test + fun `post request unfollows a single sone and redirects to return page`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "sone-id") + verifyRedirect("return.html") { + verify(core).unfollowSone(currentSone, "sone-id") + } + } + + @Test + fun `post request unfollows two sones and redirects to return page`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "sone-id1, sone-id2") + verifyRedirect("return.html") { + verify(core).unfollowSone(currentSone, "sone-id1") + verify(core).unfollowSone(currentSone, "sone-id2") + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/UnlikePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/UnlikePageTest.kt new file mode 100644 index 0000000..0de0308 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/UnlikePageTest.kt @@ -0,0 +1,61 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.web.pages.UnlikePage +import net.pterodactylus.util.web.Method +import net.pterodactylus.util.web.Method.POST +import org.junit.Test +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [UnlikePage]. + */ +class UnlikePageTest : WebPageTest() { + + private val page = UnlikePage(template, webInterface) + + override fun getPage() = page + + @Test + fun `get request does not redirect`() { + page.handleRequest(freenetRequest, templateContext) + } + + @Test + fun `post request does not remove any likes but redirects`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(currentSone, never()).removeLikedPostId(any()) + verify(currentSone, never()).removeLikedReplyId(any()) + } + } + + @Test + fun `post request removes post like and redirects`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("type", "post") + addHttpRequestParameter("id", "post-id") + verifyRedirect("return.html") { + verify(currentSone, never()).removeLikedPostId("post-id") + verify(currentSone, never()).removeLikedReplyId(any()) + } + } + + @Test + fun `post request removes reply like and redirects`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("type", "reply") + addHttpRequestParameter("id", "reply-id") + verifyRedirect("return.html") { + verify(currentSone, never()).removeLikedPostId(any()) + verify(currentSone, never()).removeLikedReplyId("reply-id") + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/UnlockSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/UnlockSonePageTest.kt new file mode 100644 index 0000000..390c1fc --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/UnlockSonePageTest.kt @@ -0,0 +1,59 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.UnlockSonePage +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [UnlockSonePage]. + */ +class UnlockSonePageTest : WebPageTest() { + + private val page = UnlockSonePage(template, webInterface) + + override fun getPage() = page + + @Test + fun `get request without sone redirects to return page`() { + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(core, never()).unlockSone(any()) + } + } + + @Test + fun `get request without invalid local sone does not unlock any sone and redirects to return page`() { + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "invalid-sone") + verifyRedirect("return.html") { + verify(core, never()).unlockSone(any()) + } + } + + @Test + fun `get request without remote sone does not unlock any sone and redirects to return page`() { + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "remote-sone") + addSone("remote-sone", mock()) + verifyRedirect("return.html") { + verify(core, never()).unlockSone(any()) + } + } + + @Test + fun `get request with local sone unlocks sone and redirects to return page`() { + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "local-sone") + val sone = mock().apply { whenever(isLocal).thenReturn(true) } + addLocalSone("local-sone", sone) + verifyRedirect("return.html") { + verify(core).unlockSone(sone) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/UntrustPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/UntrustPageTest.kt new file mode 100644 index 0000000..0a59453 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/UntrustPageTest.kt @@ -0,0 +1,59 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.web.pages.UntrustPage +import net.pterodactylus.util.web.Method.POST +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [UntrustPage]. + */ +class UntrustPageTest : WebPageTest() { + + private val page = UntrustPage(template, webInterface) + + override fun getPage() = page + + @Test + fun `get request does not redirect`() { + page.handleRequest(freenetRequest, templateContext) + verify(core, never()).untrustSone(eq(currentSone), any()) + } + + @Test + fun `post request without sone parameter does not untrust but redirects`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + verifyRedirect("return.html") { + verify(core, never()).untrustSone(eq(currentSone), any()) + } + } + + @Test + fun `post request with invalid sone parameter does not untrust but redirects`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "no-sone") + verifyRedirect("return.html") { + verify(core, never()).untrustSone(eq(currentSone), any()) + } + } + + @Test + fun `post request with valid sone parameter untrusts and redirects`() { + request("", POST) + addHttpRequestParameter("returnPage", "return.html") + addHttpRequestParameter("sone", "sone-id") + val sone = mock() + addSone("sone-id", sone) + verifyRedirect("return.html") { + verify(core).untrustSone(currentSone, sone) + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/UploadImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/UploadImagePageTest.kt new file mode 100644 index 0000000..232601b --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/UploadImagePageTest.kt @@ -0,0 +1,101 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Album +import net.pterodactylus.sone.data.Image +import net.pterodactylus.sone.data.Image.Modifier +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.data.TemporaryImage +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.mockBuilder +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.UploadImagePage +import net.pterodactylus.util.web.Method.POST +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.mockito.Mockito.any +import org.mockito.Mockito.eq +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Unit test for [UploadImagePage]. + */ +class UploadImagePageTest : WebPageTest() { + + private val parentAlbum = mock().apply { + whenever(id).thenReturn("parent-id") + whenever(sone).thenReturn(currentSone) + } + + override fun getPage() = UploadImagePage(template, webInterface) + + @Test + fun `get request does not redirect or upload anything`() { + page.handleRequest(freenetRequest, templateContext) + verify(core, never()).createTemporaryImage(any(), any()) + verify(core, never()).createImage(any(), any(), any()) + } + + @Test + fun `post request without parent results in no permission error page`() { + request("", POST) + verifyRedirect("noPermission.html") + } + + @Test + fun `post request with parent that is not the current sone results in no permission error page`() { + request("", POST) + addHttpRequestParameter("parent", "parent-id") + whenever(parentAlbum.sone).thenReturn(mock()) + addAlbum("parent-id", parentAlbum) + verifyRedirect("noPermission.html") + } + + @Test + fun `post request with empty name redirects to error page`() { + request("", POST) + addAlbum("parent-id", parentAlbum) + addHttpRequestParameter("parent", "parent-id") + addHttpRequestParameter("title", " ") + verifyRedirect("emptyImageTitle.html") + } + + @Test + fun `uploading an invalid image results in no redirect and message set in template context`() { + request("", POST) + addAlbum("parent-id", parentAlbum) + addHttpRequestParameter("parent", "parent-id") + addHttpRequestParameter("title", "title") + addUploadedFile("image", "image.png", "image/png", "no-image.png") + page.handleRequest(freenetRequest, templateContext) + verify(core, never()).createTemporaryImage(any(), any()) + assertThat(templateContext["messages"] as String?, equalTo("Page.UploadImage.Error.InvalidImage")) + } + + @Test + fun `uploading a valid image uploads image and redirects to album browser`() { + request("", POST) + addAlbum("parent-id", parentAlbum) + addHttpRequestParameter("parent", "parent-id") + addHttpRequestParameter("title", "Title") + addHttpRequestParameter("description", "Description") + addUploadedFile("image", "image.png", "image/png", "image.png") + val temporaryImage = TemporaryImage("temp-image") + val imageModifier = mockBuilder() + val image = mock().apply { + whenever(modify()).thenReturn(imageModifier) + } + whenever(core.createTemporaryImage(eq("image/png"), any())).thenReturn(temporaryImage) + whenever(core.createImage(currentSone, parentAlbum, temporaryImage)).thenReturn(image) + verifyRedirect("imageBrowser.html?album=parent-id") { + verify(image).modify() + verify(imageModifier).setWidth(2) + verify(imageModifier).setHeight(1) + verify(imageModifier).setTitle("Title") + verify(imageModifier).setDescription("Description") + verify(imageModifier).update() + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/ViewPostPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/ViewPostPageTest.kt new file mode 100644 index 0000000..bbc03bf --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/ViewPostPageTest.kt @@ -0,0 +1,87 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.Profile +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.ViewPostPage +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.nullValue +import org.junit.Test + +/** + * Unit test for [ViewPostPage]. + */ +class ViewPostPageTest : WebPageTest() { + + private val page = ViewPostPage(template, webInterface) + private val post = mock() + + override fun getPage() = page + + @Test + fun `the view post page is link-excepted`() { + assertThat(page.isLinkExcepted(null), equalTo(true)) + } + + @Test + fun `get request without parameters stores null in template context`() { + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["post"], nullValue()) + assertThat(templateContext["raw"] as? Boolean, equalTo(false)) + } + + @Test + fun `get request with invalid post id stores null in template context`() { + addHttpRequestParameter("post", "invalid-post-id") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["post"], nullValue()) + assertThat(templateContext["raw"] as? Boolean, equalTo(false)) + } + + @Test + fun `get request with valid post id stores post in template context`() { + addPost("post-id", post) + addHttpRequestParameter("post", "post-id") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["post"], equalTo(post)) + assertThat(templateContext["raw"] as? Boolean, equalTo(false)) + } + + @Test + fun `get request with valid post id and raw=true stores post in template context`() { + addPost("post-id", post) + addHttpRequestParameter("post", "post-id") + addHttpRequestParameter("raw", "true") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["post"], equalTo(post)) + assertThat(templateContext["raw"] as? Boolean, equalTo(true)) + } + + @Test + fun `page title for request without parameters is default title`() { + assertThat(page.getPageTitle(freenetRequest), equalTo("Page.ViewPost.Title")) + } + + @Test + fun `page title for request with invalid post is default title`() { + addHttpRequestParameter("post", "invalid-post-id") + assertThat(page.getPageTitle(freenetRequest), equalTo("Page.ViewPost.Title")) + } + + @Test + fun `page title for request with valid post is first twenty chars of post plus sone name plus default title`() { + whenever(currentSone.profile).thenReturn(Profile(currentSone).apply { + firstName = "First" + middleName = "M." + lastName = "Last" + }) + whenever(post.sone).thenReturn(currentSone) + whenever(post.text).thenReturn("This is a text that is longer than twenty characters.") + addPost("post-id", post) + addHttpRequestParameter("post", "post-id") + assertThat(page.getPageTitle(freenetRequest), equalTo("This is a text that … - First M. Last - Page.ViewPost.Title")) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/ViewSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/ViewSonePageTest.kt new file mode 100644 index 0000000..641e73e --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/ViewSonePageTest.kt @@ -0,0 +1,156 @@ +package net.pterodactylus.sone.web.pages + +import net.pterodactylus.sone.data.Post +import net.pterodactylus.sone.data.PostReply +import net.pterodactylus.sone.data.Profile +import net.pterodactylus.sone.data.Sone +import net.pterodactylus.sone.test.asOptional +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.pages.ViewSonePage +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.nullValue +import org.junit.Before +import org.junit.Test + +/** + * Unit test for [ViewSonePage]. + */ +class ViewSonePageTest : WebPageTest() { + + init { + whenever(currentSone.id).thenReturn("sone-id") + } + + private val page = ViewSonePage(template, webInterface) + private val post1 = createPost("post1", "First Post.", 1000, currentSone) + private val post2 = createPost("post2", "Second Post.", 2000, currentSone) + private val foreignPost1 = createPost("foreign-post1", "First Foreign Post.", 1000, mock()) + private val foreignPost2 = createPost("foreign-post2", "Second Foreign Post.", 2000, mock()) + private val directed1 = createPost("post3", "First directed.", 1500, mock(), recipient = currentSone) + private val directed2 = createPost("post4", "Second directed.", 2500, mock(), recipient = currentSone) + + override fun getPage() = page + + @Before + fun setup() { + whenever(currentSone.posts).thenReturn(mutableListOf(post2, post1)) + whenever(core.getDirectedPosts("sone-id")).thenReturn(setOf(directed1, directed2)) + core.preferences.postsPerPage = 2 + } + + @Test + fun `get request without sone parameter stores null in template context`() { + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["sone"], nullValue()) + assertThat(templateContext["soneId"], equalTo("")) + } + + @Test + fun `get request with invalid sone parameter stores null in template context`() { + addHttpRequestParameter("sone", "invalid-sone-id") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["sone"], nullValue()) + assertThat(templateContext["soneId"], equalTo("invalid-sone-id")) + } + + @Test + fun `get request with valid sone parameter stores sone in template context`() { + whenever(currentSone.posts).thenReturn(mutableListOf()) + whenever(core.getDirectedPosts("sone-id")).thenReturn(emptyList()) + addHttpRequestParameter("sone", "sone-id") + addSone("sone-id", currentSone) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["sone"], equalTo(currentSone)) + assertThat(templateContext["soneId"], equalTo("sone-id")) + } + + private fun createPost(id: String, text: String, time: Long, sender: Sone? = null, recipient: Sone? = null) = mock().apply { + whenever(this.id).thenReturn(id) + sender?.run { whenever(this@apply.sone).thenReturn(this) } + val recipientId = recipient?.id + whenever(this.recipientId).thenReturn(recipientId.asOptional()) + whenever(this.recipient).thenReturn(recipient.asOptional()) + whenever(this.time).thenReturn(time) + whenever(this.text).thenReturn(text) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `request with valid sone stores posts and directed posts in template context`() { + addSone("sone-id", currentSone) + addHttpRequestParameter("sone", "sone-id") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["posts"] as Iterable, contains(directed2, post2)) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `second page of posts is shown correctly`() { + addSone("sone-id", currentSone) + addHttpRequestParameter("sone", "sone-id") + addHttpRequestParameter("postPage", "1") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["posts"] as Iterable, contains(directed1, post1)) + } + + private fun createReply(text: String, time: Long, post: Post?) = mock().apply { + whenever(this.text).thenReturn(text) + whenever(this.time).thenReturn(time) + whenever(this.post).thenReturn(post.asOptional()) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `replies are shown correctly`() { + val reply1 = createReply("First Reply", 1500, foreignPost1) + val reply2 = createReply("Second Reply", 2500, foreignPost2) + val reply3 = createReply("Third Reply", 1750, post1) + val reply4 = createReply("Fourth Reply", 2250, post2) + val reply5 = createReply("Fifth Reply", 1600, post1) + val reply6 = createReply("Sixth Reply", 2100, directed1) + val reply7 = createReply("Seventh Reply", 2200, null) + val reply8 = createReply("Eigth Reply", 2300, foreignPost1) + whenever(currentSone.replies).thenReturn(setOf(reply1, reply2, reply3, reply4, reply5, reply6, reply7, reply8)) + whenever(core.getReplies("post1")).thenReturn(listOf(reply3, reply5)) + whenever(core.getReplies("post2")).thenReturn(listOf(reply4)) + whenever(core.getReplies("foreign-post1")).thenReturn(listOf(reply1)) + whenever(core.getReplies("foreign-post2")).thenReturn(listOf(reply2)) + whenever(core.getReplies("post3")).thenReturn(listOf(reply6)) + addSone("sone-id", currentSone) + addHttpRequestParameter("sone", "sone-id") + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["repliedPosts"] as Iterable, contains(foreignPost2, foreignPost1)) + } + + @Test + fun `page title is default for request without parameters`() { + assertThat(page.getPageTitle(freenetRequest), equalTo("Page.ViewSone.Page.TitleWithoutSone")) + } + + @Test + fun `page title is default for request with invalid sone parameters`() { + addHttpRequestParameter("sone", "invalid-sone-id") + assertThat(page.getPageTitle(freenetRequest), equalTo("Page.ViewSone.Page.TitleWithoutSone")) + } + + @Test + fun `page title contains sone name for request with sone parameters`() { + addHttpRequestParameter("sone", "sone-id") + addSone("sone-id", currentSone) + whenever(currentSone.profile).thenReturn(Profile(currentSone).apply { + firstName = "First" + middleName = "M." + lastName = "Last" + }) + assertThat(page.getPageTitle(freenetRequest), equalTo("First M. Last - Page.ViewSone.Title")) + } + + @Test + fun `page is link-excepted`() { + assertThat(page.isLinkExcepted(null), equalTo(true)) + } + +} diff --git a/src/test/resources/net/pterodactylus/sone/web/image.png b/src/test/resources/net/pterodactylus/sone/web/image.png deleted file mode 100644 index 6daa837..0000000 Binary files a/src/test/resources/net/pterodactylus/sone/web/image.png and /dev/null differ diff --git a/src/test/resources/net/pterodactylus/sone/web/no-image.png b/src/test/resources/net/pterodactylus/sone/web/no-image.png deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/resources/net/pterodactylus/sone/web/pages/image.png b/src/test/resources/net/pterodactylus/sone/web/pages/image.png new file mode 100644 index 0000000..6daa837 Binary files /dev/null and b/src/test/resources/net/pterodactylus/sone/web/pages/image.png differ diff --git a/src/test/resources/net/pterodactylus/sone/web/pages/no-image.png b/src/test/resources/net/pterodactylus/sone/web/pages/no-image.png new file mode 100644 index 0000000..e69de29