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;
+++ /dev/null
-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}")
- }
- }
-
-}
+++ /dev/null
-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)
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<Field> 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;
- }
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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> sone = webInterface.getCore().getSone(soneId);
- if (sone.isPresent()) {
- webInterface.getCore().followSone(currentSone, soneId);
- webInterface.getCore().markSoneKnown(sone.get());
- }
- }
- throw new RedirectException(returnPage);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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;
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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> 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<Album> albums = new ArrayList<Album>();
- for (Sone sone : webInterface.getCore().getSones()) {
- albums.addAll(from(sone.getRootAlbum().getAlbums()).transformAndConcat(FLATTENER).filter(NOT_EMPTY).toList());
- }
- Collections.sort(albums, TITLE_COMPARATOR);
- Pagination<Album> albumPagination = new Pagination<Album>(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;
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<Post> allPosts = new ArrayList<Post>();
- allPosts.addAll(currentSone.getPosts());
- for (String friendSoneId : currentSone.getFriends()) {
- Optional<Sone> 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<Post> sortedPosts = new ArrayList<Post>(allPosts);
- Collections.sort(sortedPosts, Post.NEWEST_FIRST);
- Pagination<Post> pagination = new Pagination<Post>(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0));
- templateContext.set("pagination", pagination);
- templateContext.set("posts", pagination.getItems());
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<Sone> knownSones = Collections2.filter(webInterface.getCore().getSones(), Sone.EMPTY_SONE_FILTER);
- if ((currentSone != null) && "followed".equals(filter)) {
- knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
-
- @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<Sone>() {
-
- @Override
- public boolean apply(Sone sone) {
- return !currentSone.hasFriend(sone.getId());
- }
- });
- } else if ("new".equals(filter)) {
- knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean apply(Sone sone) {
- return !sone.isKnown();
- }
- });
- } else if ("not-new".equals(filter)) {
- knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
-
- /**
- * {@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<Sone> sortedSones = new ArrayList<Sone>(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<Sone> sonePagination = new Pagination<Sone>(sortedSones, 25).setPage(parseInt(request.getHttpRequest().getParam("page"), 0));
- templateContext.set("pagination", sonePagination);
- templateContext.set("knownSones", sonePagination.getItems());
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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> post = webInterface.getCore().getPost(id);
- if (!post.isPresent()) {
- continue;
- }
- webInterface.getCore().markPostKnown(post.get());
- } else if (type.equals("reply")) {
- Optional<PostReply> reply = webInterface.getCore().getPostReply(id);
- if (!reply.isPresent()) {
- continue;
- }
- webInterface.getCore().markReplyKnown(reply.get());
- } else {
- Optional<Sone> 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);
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<Post> posts = new HashSet<Post>(webInterface.getNewPosts(getCurrentSoneWithoutCreatingSession(request.getToadletContext())));
- for (PostReply reply : webInterface.getNewReplies(getCurrentSoneWithoutCreatingSession(request.getToadletContext()))) {
- posts.add(reply.getPost().get());
- }
-
- /* filter and sort them. */
- List<Post> sortedPosts = new ArrayList<>(posts);
- Collections.sort(sortedPosts, Post.NEWEST_FIRST);
-
- /* paginate them. */
- Pagination<Post> pagination = new Pagination<>(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0));
- templateContext.set("pagination", pagination);
- templateContext.set("posts", pagination.getItems());
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<String> fieldErrors = new ArrayList<String>();
- 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());
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <REQ>
- * The type of the request
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ReloadingPage<REQ extends Request> implements Page<REQ> {
-
- 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);
- }
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class SearchPage extends SoneTemplatePage {
-
- /** The logger. */
- private static final Logger logger = getLogger(SearchPage.class.getName());
-
- /** Short-term cache. */
- private final LoadingCache<List<Phrase>, Set<Hit<Post>>> hitCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<List<Phrase>, Set<Hit<Post>>>() {
-
- @Override
- @SuppressWarnings("synthetic-access")
- public Set<Hit<Post>> load(List<Phrase> phrases) {
- Set<Post> posts = new HashSet<Post>();
- 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<Phrase> 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<Sone> sones = webInterface.getCore().getSones();
- Collection<Hit<Sone>> soneHits = getHits(sones, phrases, SoneStringGenerator.COMPLETE_GENERATOR);
-
- Collection<Hit<Post>> postHits = hitCache.getUnchecked(phrases);
-
- /* now filter. */
- soneHits = Collections2.filter(soneHits, Hit.POSITIVE_FILTER);
- postHits = Collections2.filter(postHits, Hit.POSITIVE_FILTER);
-
- /* now sort. */
- List<Hit<Sone>> sortedSoneHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(soneHits);
- List<Hit<Post>> sortedPostHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(postHits);
-
- /* extract Sones and posts. */
- List<Sone> resultSones = FluentIterable.from(sortedSoneHits).transform(new HitMapper<Sone>()).toList();
- List<Post> resultPosts = FluentIterable.from(sortedPostHits).transform(new HitMapper<Post>()).toList();
-
- /* pagination. */
- Pagination<Sone> sonePagination = new Pagination<Sone>(resultSones, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(fromNullable(tryParse(request.getHttpRequest().getParam("sonePage"))).or(0));
- Pagination<Post> postPagination = new Pagination<Post>(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 <T>
- * 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 <T> Set<Hit<T>> getHits(Collection<T> objects, List<Phrase> phrases, StringGenerator<T> stringGenerator) {
- Set<Hit<T>> hits = new HashSet<Hit<T>>();
- for (T object : objects) {
- String objectString = stringGenerator.generateString(object);
- double score = calculateScore(phrases, objectString);
- hits.add(new Hit<T>(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<Phrase> parseSearchPhrases(String query) {
- List<String> parsedPhrases;
- try {
- parsedPhrases = StringEscaper.parseLine(query);
- } catch (TextException te1) {
- /* invalid query. */
- return Collections.emptyList();
- }
-
- List<Phrase> phrases = new ArrayList<Phrase>();
- 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<Phrase> 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> 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 <T>
- * The type of the objects
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
- private static interface StringGenerator<T> {
-
- /**
- * 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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
- private static class SoneStringGenerator implements StringGenerator<Sone> {
-
- /** 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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
- private class PostStringGenerator implements StringGenerator<Post> {
-
- /**
- * {@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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
- private static class Phrase {
-
- /**
- * The optionality of a search phrase.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’
- * Roden</a>
- */
- 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 <T>
- * The type of the searched object
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
- private static class Hit<T> {
-
- /** Filter for {@link Hit}s with a score of more than 0. */
- public static final Predicate<Hit<?>> POSITIVE_FILTER = new Predicate<Hit<?>>() {
-
- @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<Hit<?>> DESCENDING_COMPARATOR = new Comparator<Hit<?>>() {
-
- @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 <T>
- * The type of the object to extract
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
- private static class HitMapper<T> implements Function<Hit<T>, T> {
-
- /**
- * {@inheritDoc}
- */
- @Override
- public T apply(Hit<T> input) {
- return input.getObject();
- }
-
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<Map<String, String>> getAdditionalLinkNodes(FreenetRequest request) {
- return ImmutableList.<Map<String, String>> builder().add(ImmutableMap.<String, String> 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<String> 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<Notification> notifications = new ArrayList<Notification>(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;
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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> sone = webInterface.getCore().getSone(identity);
- if (sone.isPresent()) {
- webInterface.getCore().trustSone(currentSone, sone.get());
- }
- throw new RedirectException(returnPage);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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> 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<Post> posts = webInterface.getCore().getBookmarkedPosts();
- for (Post post : posts) {
- if (!post.isLoaded()) {
- webInterface.getCore().unbookmarkPost(post);
- }
- }
- throw new RedirectException("bookmarks.html");
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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);
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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> sone = webInterface.getCore().getSone(identity);
- if (sone.isPresent()) {
- webInterface.getCore().untrustSone(currentSone, sone.get());
- }
- throw new RedirectException(returnPage);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<ImageReader> 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;
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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> 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> post = webInterface.getCore().getPost(postId);
- templateContext.set("post", post.orNull());
- templateContext.set("raw", raw);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean isLinkExcepted(URI link) {
- return true;
- }
-
-}
+++ /dev/null
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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> 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> sone = webInterface.getCore().getSone(soneId);
- templateContext.set("sone", sone.orNull());
- templateContext.set("soneId", soneId);
- if (!sone.isPresent()) {
- return;
- }
- List<Post> sonePosts = sone.get().getPosts();
- sonePosts.addAll(webInterface.getCore().getDirectedPosts(sone.get().getId()));
- Collections.sort(sonePosts, Post.NEWEST_FIRST);
- Pagination<Post> postPagination = new Pagination<Post>(sonePosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("postPage"), 0));
- templateContext.set("postPagination", postPagination);
- templateContext.set("posts", postPagination.getItems());
- Set<PostReply> replies = sone.get().getReplies();
- final Map<Post, List<PostReply>> repliedPosts = new HashMap<Post, List<PostReply>>();
- for (PostReply reply : replies) {
- Optional<Post> 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<Post> posts = new ArrayList<Post>(repliedPosts.keySet());
- Collections.sort(posts, new Comparator<Post>() {
-
- @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<Post> repliedPostPagination = new Pagination<Post>(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;
- }
-
-}
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;
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<Field> 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;
+ }
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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> sone = webInterface.getCore().getSone(soneId);
+ if (sone.isPresent()) {
+ webInterface.getCore().followSone(currentSone, soneId);
+ webInterface.getCore().markSoneKnown(sone.get());
+ }
+ }
+ throw new RedirectException(returnPage);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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;
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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> 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<Album> albums = new ArrayList<Album>();
+ for (Sone sone : webInterface.getCore().getSones()) {
+ albums.addAll(from(sone.getRootAlbum().getAlbums()).transformAndConcat(FLATTENER).filter(NOT_EMPTY).toList());
+ }
+ Collections.sort(albums, TITLE_COMPARATOR);
+ Pagination<Album> albumPagination = new Pagination<Album>(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;
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<Post> allPosts = new ArrayList<Post>();
+ allPosts.addAll(currentSone.getPosts());
+ for (String friendSoneId : currentSone.getFriends()) {
+ Optional<Sone> 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<Post> sortedPosts = new ArrayList<Post>(allPosts);
+ Collections.sort(sortedPosts, Post.NEWEST_FIRST);
+ Pagination<Post> pagination = new Pagination<Post>(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0));
+ templateContext.set("pagination", pagination);
+ templateContext.set("posts", pagination.getItems());
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<Sone> knownSones = Collections2.filter(webInterface.getCore().getSones(), Sone.EMPTY_SONE_FILTER);
+ if ((currentSone != null) && "followed".equals(filter)) {
+ knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
+
+ @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<Sone>() {
+
+ @Override
+ public boolean apply(Sone sone) {
+ return !currentSone.hasFriend(sone.getId());
+ }
+ });
+ } else if ("new".equals(filter)) {
+ knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean apply(Sone sone) {
+ return !sone.isKnown();
+ }
+ });
+ } else if ("not-new".equals(filter)) {
+ knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
+
+ /**
+ * {@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<Sone> sortedSones = new ArrayList<Sone>(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<Sone> sonePagination = new Pagination<Sone>(sortedSones, 25).setPage(parseInt(request.getHttpRequest().getParam("page"), 0));
+ templateContext.set("pagination", sonePagination);
+ templateContext.set("knownSones", sonePagination.getItems());
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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> post = webInterface.getCore().getPost(id);
+ if (!post.isPresent()) {
+ continue;
+ }
+ webInterface.getCore().markPostKnown(post.get());
+ } else if (type.equals("reply")) {
+ Optional<PostReply> reply = webInterface.getCore().getPostReply(id);
+ if (!reply.isPresent()) {
+ continue;
+ }
+ webInterface.getCore().markReplyKnown(reply.get());
+ } else {
+ Optional<Sone> 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<Post> posts = new HashSet<Post>(webInterface.getNewPosts(getCurrentSoneWithoutCreatingSession(request.getToadletContext())));
+ for (PostReply reply : webInterface.getNewReplies(getCurrentSoneWithoutCreatingSession(request.getToadletContext()))) {
+ posts.add(reply.getPost().get());
+ }
+
+ /* filter and sort them. */
+ List<Post> sortedPosts = new ArrayList<>(posts);
+ Collections.sort(sortedPosts, Post.NEWEST_FIRST);
+
+ /* paginate them. */
+ Pagination<Post> pagination = new Pagination<>(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("page"), 0));
+ templateContext.set("pagination", pagination);
+ templateContext.set("posts", pagination.getItems());
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<String> fieldErrors = new ArrayList<String>();
+ 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());
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <REQ>
+ * The type of the request
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ReloadingPage<REQ extends Request> implements Page<REQ> {
+
+ 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);
+ }
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SearchPage extends SoneTemplatePage {
+
+ /** The logger. */
+ private static final Logger logger = getLogger(SearchPage.class.getName());
+
+ /** Short-term cache. */
+ private final LoadingCache<List<Phrase>, Set<Hit<Post>>> hitCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<List<Phrase>, Set<Hit<Post>>>() {
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public Set<Hit<Post>> load(List<Phrase> phrases) {
+ Set<Post> posts = new HashSet<Post>();
+ 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<Phrase> 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<Sone> sones = webInterface.getCore().getSones();
+ Collection<Hit<Sone>> soneHits = getHits(sones, phrases, SoneStringGenerator.COMPLETE_GENERATOR);
+
+ Collection<Hit<Post>> postHits = hitCache.getUnchecked(phrases);
+
+ /* now filter. */
+ soneHits = Collections2.filter(soneHits, Hit.POSITIVE_FILTER);
+ postHits = Collections2.filter(postHits, Hit.POSITIVE_FILTER);
+
+ /* now sort. */
+ List<Hit<Sone>> sortedSoneHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(soneHits);
+ List<Hit<Post>> sortedPostHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(postHits);
+
+ /* extract Sones and posts. */
+ List<Sone> resultSones = FluentIterable.from(sortedSoneHits).transform(new HitMapper<Sone>()).toList();
+ List<Post> resultPosts = FluentIterable.from(sortedPostHits).transform(new HitMapper<Post>()).toList();
+
+ /* pagination. */
+ Pagination<Sone> sonePagination = new Pagination<Sone>(resultSones, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(fromNullable(tryParse(request.getHttpRequest().getParam("sonePage"))).or(0));
+ Pagination<Post> postPagination = new Pagination<Post>(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 <T>
+ * 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 <T> Set<Hit<T>> getHits(Collection<T> objects, List<Phrase> phrases, StringGenerator<T> stringGenerator) {
+ Set<Hit<T>> hits = new HashSet<Hit<T>>();
+ for (T object : objects) {
+ String objectString = stringGenerator.generateString(object);
+ double score = calculateScore(phrases, objectString);
+ hits.add(new Hit<T>(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<Phrase> parseSearchPhrases(String query) {
+ List<String> parsedPhrases;
+ try {
+ parsedPhrases = StringEscaper.parseLine(query);
+ } catch (TextException te1) {
+ /* invalid query. */
+ return Collections.emptyList();
+ }
+
+ List<Phrase> phrases = new ArrayList<Phrase>();
+ 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<Phrase> 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> 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 <T>
+ * The type of the objects
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ private static interface StringGenerator<T> {
+
+ /**
+ * 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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ private static class SoneStringGenerator implements StringGenerator<Sone> {
+
+ /** 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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ private class PostStringGenerator implements StringGenerator<Post> {
+
+ /**
+ * {@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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ private static class Phrase {
+
+ /**
+ * The optionality of a search phrase.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’
+ * Roden</a>
+ */
+ 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 <T>
+ * The type of the searched object
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ private static class Hit<T> {
+
+ /** Filter for {@link Hit}s with a score of more than 0. */
+ public static final Predicate<Hit<?>> POSITIVE_FILTER = new Predicate<Hit<?>>() {
+
+ @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<Hit<?>> DESCENDING_COMPARATOR = new Comparator<Hit<?>>() {
+
+ @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 <T>
+ * The type of the object to extract
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+ private static class HitMapper<T> implements Function<Hit<T>, T> {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T apply(Hit<T> input) {
+ return input.getObject();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<Map<String, String>> getAdditionalLinkNodes(FreenetRequest request) {
+ return ImmutableList.<Map<String, String>> builder().add(ImmutableMap.<String, String> 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<String> 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<Notification> notifications = new ArrayList<Notification>(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;
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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> sone = webInterface.getCore().getSone(identity);
+ if (sone.isPresent()) {
+ webInterface.getCore().trustSone(currentSone, sone.get());
+ }
+ throw new RedirectException(returnPage);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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> 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<Post> posts = webInterface.getCore().getBookmarkedPosts();
+ for (Post post : posts) {
+ if (!post.isLoaded()) {
+ webInterface.getCore().unbookmarkPost(post);
+ }
+ }
+ throw new RedirectException("bookmarks.html");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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);
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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> sone = webInterface.getCore().getSone(identity);
+ if (sone.isPresent()) {
+ webInterface.getCore().untrustSone(currentSone, sone.get());
+ }
+ throw new RedirectException(returnPage);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<ImageReader> 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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> 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> post = webInterface.getCore().getPost(postId);
+ templateContext.set("post", post.orNull());
+ templateContext.set("raw", raw);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isLinkExcepted(URI link) {
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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> 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> sone = webInterface.getCore().getSone(soneId);
+ templateContext.set("sone", sone.orNull());
+ templateContext.set("soneId", soneId);
+ if (!sone.isPresent()) {
+ return;
+ }
+ List<Post> sonePosts = sone.get().getPosts();
+ sonePosts.addAll(webInterface.getCore().getDirectedPosts(sone.get().getId()));
+ Collections.sort(sonePosts, Post.NEWEST_FIRST);
+ Pagination<Post> postPagination = new Pagination<Post>(sonePosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(parseInt(request.getHttpRequest().getParam("postPage"), 0));
+ templateContext.set("postPagination", postPagination);
+ templateContext.set("posts", postPagination.getItems());
+ Set<PostReply> replies = sone.get().getReplies();
+ final Map<Post, List<PostReply>> repliedPosts = new HashMap<Post, List<PostReply>>();
+ for (PostReply reply : replies) {
+ Optional<Post> 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<Post> posts = new ArrayList<Post>(repliedPosts.keySet());
+ Collections.sort(posts, new Comparator<Post>() {
+
+ @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<Post> repliedPostPagination = new Pagination<Post>(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;
+ }
+
+}
+++ /dev/null
-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
- }
-
-}
+++ /dev/null
-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)
- }
- }
-
-}
+++ /dev/null
-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<Post>(posts.filter { it.isLoaded }.sortedByDescending { it.time }, webInterface.core.preferences.postsPerPage)
- templateContext["pagination"] = pagination
- templateContext["posts"] = pagination.items
- templateContext["postsNotLoaded"] = posts.any { !it.isLoaded }
- }
- }
-
-}
+++ /dev/null
-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)
- }
- }
-
-}
+++ /dev/null
-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)
- }
-
-}
+++ /dev/null
-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}")
- }
- }
-
-}
+++ /dev/null
-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
- }
-
-}
+++ /dev/null
-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
- }
-
-}
+++ /dev/null
-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")
- }
- }
-
-}
+++ /dev/null
-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)
- }
- }
- }
-
-}
+++ /dev/null
-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")
- }
- }
-
-}
+++ /dev/null
-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)
- }
-
-}
+++ /dev/null
-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))
- }
- }
-
-}
+++ /dev/null
-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}")
- }
- }
- }
-
-}
+++ /dev/null
-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
- }
-
-}
--- /dev/null
+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
+ }
+
+}
--- /dev/null
+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)
+ }
+ }
+
+}
--- /dev/null
+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<Post>(posts.filter { it.isLoaded }.sortedByDescending { it.time }, webInterface.core.preferences.postsPerPage)
+ templateContext["pagination"] = pagination
+ templateContext["posts"] = pagination.items
+ templateContext["postsNotLoaded"] = posts.any { !it.isLoaded }
+ }
+ }
+
+}
--- /dev/null
+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}")
+ }
+ }
+
+}
--- /dev/null
+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)
+ }
+ }
+
+}
--- /dev/null
+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)
+ }
+ }
+
+}
--- /dev/null
+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)
+ }
+
+}
--- /dev/null
+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}")
+ }
+ }
+
+}
--- /dev/null
+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
+ }
+
+}
--- /dev/null
+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
+ }
+
+}
--- /dev/null
+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")
+ }
+ }
+
+}
--- /dev/null
+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)
+ }
+ }
+ }
+
+}
--- /dev/null
+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")
+ }
+ }
+
+}
--- /dev/null
+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)
+ }
+
+}
--- /dev/null
+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))
+ }
+ }
+
+}
--- /dev/null
+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}")
+ }
+ }
+ }
+
+}
--- /dev/null
+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
+ }
+
+}
+++ /dev/null
-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<Any>(true))
- }
-
- private fun createAlbum(albumId: String) = deepMock<Album>().apply {
- whenever(id).thenReturn(albumId)
- selfMock<Album.Modifier>().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")
- }
- }
-
-}
+++ /dev/null
-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<Post>().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<Post>().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<Sone>().apply { addLocalSone("sender-id", this) }
- val post = mock<Post>().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<Any>(true))
- assertThat(templateContext["returnPage"], equalTo<Any>("return.html"))
- assertThat(templateContext["postId"], equalTo<Any>("post-id"))
- assertThat(templateContext["text"], equalTo<Any>(""))
- }
-
- @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<Any>("return.html"))
- assertThat(templateContext["postId"], equalTo<Any>("post-id"))
- assertThat(templateContext["text"], equalTo<Any>("new text"))
- }
-
-}
+++ /dev/null
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<Post> posts = asList(mock(Post.class), mock(Post.class));
- List<PostReply> 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<Post> renderedPosts = templateContext.get("posts", List.class);
- assertThat(renderedPosts, containsInAnyOrder(posts.get(0), posts.get(1), extraPost));
- }
-
-}
+++ /dev/null
-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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-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<String, String> requestParameters = ArrayListMultimap.create();
- protected final Map<String, String> requestHeaders = new HashMap<>();
- private final Map<String, String> uploadedFilesNames = new HashMap<>();
- private final Map<String, String> uploadedFilesContentTypes = new HashMap<>();
- private final Map<String, String> 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<OwnIdentity> ownIdentities = new HashSet<>();
- private final Map<String, Sone> sones = new HashMap<>();
- protected final List<Sone> localSones = new ArrayList<>();
- private final ListMultimap<String, PostReply> 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<String[]>() {
- @Override
- public String[] answer(InvocationOnMock invocation) throws Throwable {
- return requestParameters.get(invocation.<String>getArgument(0)).toArray(new String[0]);
- }
- });
- when(httpRequest.getPartAsStringFailsafe(anyString(), anyInt())).thenAnswer(new Answer<String>() {
- @Override
- public String answer(InvocationOnMock invocation) throws Throwable {
- String parameter = invocation.getArgument(0);
- int maxLength = invocation.getArgument(1);
- Collection<String> 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<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- return !requestParameters.isEmpty();
- }
- });
- when(httpRequest.getParameterNames()).thenAnswer(new Answer<Collection<String>>() {
- @Override
- public Collection<String> answer(InvocationOnMock invocation) throws Throwable {
- return requestParameters.keySet();
- }
- });
- when(httpRequest.getParam(anyString())).thenAnswer(new Answer<String>() {
- @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.<String>any())).thenAnswer(new Answer<String>() {
- @Override
- public String answer(InvocationOnMock invocation) throws Throwable {
- String parameter = invocation.getArgument(0);
- return requestParameters.containsKey(parameter) ? requestParameters.get(parameter).iterator().next() : invocation.<String>getArgument(1);
- }
- });
- when(httpRequest.isParameterSet(anyString())).thenAnswer(new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- return requestParameters.containsKey(invocation.<String>getArgument(0)) &&
- requestParameters.get(invocation.<String>getArgument(0)).iterator().next() != null;
- }
- });
- when(httpRequest.isPartSet(anyString())).thenAnswer(new Answer<Boolean>() {
- @Override
- public Boolean answer(InvocationOnMock invocation) throws Throwable {
- return requestParameters.containsKey(invocation.<String>getArgument(0)) &&
- requestParameters.get(invocation.<String>getArgument(0)).iterator().next() != null;
- }
- });
- when(httpRequest.getParts()).thenAnswer(new Answer<String[]>() {
- @Override
- public String[] answer(InvocationOnMock invocation) throws Throwable {
- return requestParameters.keySet().toArray(new String[requestParameters.size()]);
- }
- });
- when(httpRequest.getHeader(anyString())).thenAnswer(new Answer<String>() {
- @Override
- public String answer(InvocationOnMock invocation) throws Throwable {
- return requestHeaders.get(invocation.<String>getArgument(0).toLowerCase());
- }
- });
- when(httpRequest.getUploadedFile(anyString())).thenAnswer(new Answer<HTTPUploadedFile>() {
- @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.<Sone>absent());
- when(core.getSones()).thenAnswer(new Answer<Collection<Sone>>() {
- @Override
- public Collection<Sone> answer(InvocationOnMock invocation) throws Throwable {
- return sones.values();
- }
- });
- when(core.getSone(anyString())).thenAnswer(new Answer<Optional<Sone>>() {
- @Override
- public Optional<Sone> answer(InvocationOnMock invocation) throws Throwable {
- return Optional.fromNullable(sones.get(invocation.getArgument(0)));
- }
- });
- when(core.getPost(anyString())).thenReturn(Optional.<Post>absent());
- when(core.getPostReply(anyString())).thenReturn(Optional.<PostReply>absent());
- when(core.getReplies(anyString())).thenAnswer(new Answer<List<PostReply>>() {
- @Override
- public List<PostReply> answer(InvocationOnMock invocation) throws Throwable {
- return postReplies.get(invocation.<String>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<String>() {
- @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.<Notification>absent());
- when(webInterface.getNotifications(currentSone)).thenReturn(new ArrayList<Notification>());
- }
-
- @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();
- }
- }
-
-}
--- /dev/null
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<Post> posts = asList(mock(Post.class), mock(Post.class));
+ List<PostReply> 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<Post> renderedPosts = templateContext.get("posts", List.class);
+ assertThat(renderedPosts, containsInAnyOrder(posts.get(0), posts.get(1), extraPost));
+ }
+
+}
--- /dev/null
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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<String, String> requestParameters = ArrayListMultimap.create();
+ protected final Map<String, String> requestHeaders = new HashMap<>();
+ private final Map<String, String> uploadedFilesNames = new HashMap<>();
+ private final Map<String, String> uploadedFilesContentTypes = new HashMap<>();
+ private final Map<String, String> 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<OwnIdentity> ownIdentities = new HashSet<>();
+ private final Map<String, Sone> sones = new HashMap<>();
+ protected final List<Sone> localSones = new ArrayList<>();
+ private final ListMultimap<String, PostReply> 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<String[]>() {
+ @Override
+ public String[] answer(InvocationOnMock invocation) throws Throwable {
+ return requestParameters.get(invocation.<String>getArgument(0)).toArray(new String[0]);
+ }
+ });
+ when(httpRequest.getPartAsStringFailsafe(anyString(), anyInt())).thenAnswer(new Answer<String>() {
+ @Override
+ public String answer(InvocationOnMock invocation) throws Throwable {
+ String parameter = invocation.getArgument(0);
+ int maxLength = invocation.getArgument(1);
+ Collection<String> 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<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ return !requestParameters.isEmpty();
+ }
+ });
+ when(httpRequest.getParameterNames()).thenAnswer(new Answer<Collection<String>>() {
+ @Override
+ public Collection<String> answer(InvocationOnMock invocation) throws Throwable {
+ return requestParameters.keySet();
+ }
+ });
+ when(httpRequest.getParam(anyString())).thenAnswer(new Answer<String>() {
+ @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.<String>any())).thenAnswer(new Answer<String>() {
+ @Override
+ public String answer(InvocationOnMock invocation) throws Throwable {
+ String parameter = invocation.getArgument(0);
+ return requestParameters.containsKey(parameter) ? requestParameters.get(parameter).iterator().next() : invocation.<String>getArgument(1);
+ }
+ });
+ when(httpRequest.isParameterSet(anyString())).thenAnswer(new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ return requestParameters.containsKey(invocation.<String>getArgument(0)) &&
+ requestParameters.get(invocation.<String>getArgument(0)).iterator().next() != null;
+ }
+ });
+ when(httpRequest.isPartSet(anyString())).thenAnswer(new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ return requestParameters.containsKey(invocation.<String>getArgument(0)) &&
+ requestParameters.get(invocation.<String>getArgument(0)).iterator().next() != null;
+ }
+ });
+ when(httpRequest.getParts()).thenAnswer(new Answer<String[]>() {
+ @Override
+ public String[] answer(InvocationOnMock invocation) throws Throwable {
+ return requestParameters.keySet().toArray(new String[requestParameters.size()]);
+ }
+ });
+ when(httpRequest.getHeader(anyString())).thenAnswer(new Answer<String>() {
+ @Override
+ public String answer(InvocationOnMock invocation) throws Throwable {
+ return requestHeaders.get(invocation.<String>getArgument(0).toLowerCase());
+ }
+ });
+ when(httpRequest.getUploadedFile(anyString())).thenAnswer(new Answer<HTTPUploadedFile>() {
+ @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.<Sone>absent());
+ when(core.getSones()).thenAnswer(new Answer<Collection<Sone>>() {
+ @Override
+ public Collection<Sone> answer(InvocationOnMock invocation) throws Throwable {
+ return sones.values();
+ }
+ });
+ when(core.getSone(anyString())).thenAnswer(new Answer<Optional<Sone>>() {
+ @Override
+ public Optional<Sone> answer(InvocationOnMock invocation) throws Throwable {
+ return Optional.fromNullable(sones.get(invocation.getArgument(0)));
+ }
+ });
+ when(core.getPost(anyString())).thenReturn(Optional.<Post>absent());
+ when(core.getPostReply(anyString())).thenReturn(Optional.<PostReply>absent());
+ when(core.getReplies(anyString())).thenAnswer(new Answer<List<PostReply>>() {
+ @Override
+ public List<PostReply> answer(InvocationOnMock invocation) throws Throwable {
+ return postReplies.get(invocation.<String>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<String>() {
+ @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.<Notification>absent());
+ when(webInterface.getNotifications(currentSone)).thenReturn(new ArrayList<Notification>());
+ }
+
+ @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();
+ }
+ }
+
+}
+++ /dev/null
-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<Any>(version))
- }
-
- @Test
- fun `page sets correct homepage in template context`() {
- page.processTemplate(freenetRequest, templateContext)
- assertThat(templateContext["homepage"], equalTo<Any>(homepage))
- }
-
- @Test
- fun `page sets correct year in template context`() {
- page.processTemplate(freenetRequest, templateContext)
- assertThat(templateContext["year"], equalTo<Any>(year))
- }
-
-}
+++ /dev/null
-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<Post>()
- 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())
- }
- }
-
-}
+++ /dev/null
-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<Post>().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<Post>, contains(post2, post3, post1))
- assertThat((templateContext["pagination"] as Pagination<Post>).items, contains(post2, post3, post1))
- assertThat(templateContext["postsNotLoaded"], equalTo<Any>(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<Post>, contains(post2, post1))
- assertThat((templateContext["pagination"] as Pagination<Post>).items, contains(post2, post1))
- assertThat(templateContext["postsNotLoaded"], equalTo<Any>(true))
- }
-
-}
+++ /dev/null
-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<Any>("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<Any>(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<Sone>()
- 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<Sone>()
- 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")
- }
- }
-
-}
+++ /dev/null
-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<Sone>().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<OwnIdentity>().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<String>(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<Sone>, 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<OwnIdentity>, contains(ownIdentities_[1], ownIdentities_[2]))
- }
-
- @Test
- fun `sone is created and logged in`() {
- addExistingOwnIdentities()
- request("", POST)
- addHttpRequestParameter("identity", "own-id-3")
- val newSone = mock<Sone>()
- 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<Any>(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))
- }
-
-}
+++ /dev/null
-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<Sone>()
- private val album = mock<Album>()
- private val parentAlbum = mock<Album>()
-
- 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<Album>()
- addAlbum("album-id", album)
- addHttpRequestParameter("album", "album-id")
- page.processTemplate(freenetRequest, templateContext)
- assertThat(templateContext["album"], equalTo<Any>(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<Album>())
- addAlbum("album-id", album)
- addHttpRequestParameter("album", "album-id")
- verifyRedirect("imageBrowser.html?album=parent-id") {
- verify(core).deleteAlbum(album)
- }
- }
-
-}
+++ /dev/null
-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<Image>()
- private val sone = mock<Sone>()
-
- override fun getPage() = page
-
- @Before
- fun setupImage() {
- val album = mock<Album>()
- 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<Any>(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)
- }
- }
-
-}
+++ /dev/null
-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<Post>()
- private val sone = mock<Sone>()
-
- 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<Any>(post))
- assertThat(templateContext["returnPage"], equalTo<Any>("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<Any>(post))
- assertThat(templateContext["returnPage"], equalTo<Any>("return.html"))
- }
-
-}
+++ /dev/null
-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<Any>(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
- }
- }
-
-}
+++ /dev/null
-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<Sone>()
- private val reply = mock<PostReply>()
-
- 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<Any>("reply-id"))
- assertThat(templateContext["returnPage"], equalTo<Any>("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<Any>("reply-id"))
- assertThat(templateContext["returnPage"], equalTo<Any>("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)
- }
- }
-
-}
+++ /dev/null
-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)
- }
- }
-
-}
+++ /dev/null
-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<Notification>()
-
- 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()
- }
- }
-
-}
+++ /dev/null
-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<Sone>()
- addSone("remote-sone-id", remoteSone)
- addHttpRequestParameter("returnPage", "return.html")
- addHttpRequestParameter("sone", "remote-sone-id")
- verifyRedirect("return.html") {
- verify(core).distrustSone(currentSone, remoteSone)
- }
- }
-
-}
+++ /dev/null
-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<Album>()
- private val parentAlbum = mock<Album>()
- private val modifier = mockBuilder<Album.Modifier>()
- private val sone = mock<Sone>()
-
- 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()
- }
- }
-
-}
+++ /dev/null
-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<Image>()
- private val modifier = mockBuilder<Image.Modifier>()
- private val sone = mock<Sone>()
- private val album = mock<Album>()
-
- 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()
- }
- }
-
-}
+++ /dev/null
-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<Any>(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<Any>(true))
- }
-
-}
+++ /dev/null
-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<Image>()
- 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<Any>("First"))
- assertThat(templateContext["middleName"], equalTo<Any>("Middle"))
- assertThat(templateContext["lastName"], equalTo<Any>("Last"))
- assertThat(templateContext["birthDay"], equalTo<Any>(31))
- assertThat(templateContext["birthMonth"], equalTo<Any>(12))
- assertThat(templateContext["birthYear"], equalTo<Any>(1999))
- assertThat(templateContext["avatarId"], equalTo<Any>("image-id"))
- assertThat(templateContext["fields"], equalTo<Any>(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<Any>("First"))
- assertThat(templateContext["middleName"], equalTo<Any>("Middle"))
- assertThat(templateContext["lastName"], equalTo<Any>("Last"))
- assertThat(templateContext["birthDay"], equalTo<Any>(31))
- assertThat(templateContext["birthMonth"], equalTo<Any>(12))
- assertThat(templateContext["birthYear"], equalTo<Any>(1999))
- assertThat(templateContext["avatarId"], equalTo<Any>("image-id"))
- assertThat(templateContext["fields"], equalTo<Any>(listOf(firstField, secondField)))
- }
-
- private fun <T> 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<Image>()
- 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<Any>("new-field"))
- assertThat(templateContext["duplicateFieldName"], equalTo<Any>(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}")
- }
-
-}
+++ /dev/null
-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<Sone>()
- 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<Sone>()
- addSone("sone-id1", firstSone)
- val secondSone = mock<Sone>()
- 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<Sone>())
- }
- }
-
-}
+++ /dev/null
-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)))
- }
-
-}
+++ /dev/null
-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<Album>()
- addAlbum("album-id", album)
- addHttpRequestParameter("album", "album-id")
- addHttpRequestParameter("page", "5")
- page.handleRequest(freenetRequest, templateContext)
- assertThat(templateContext["albumRequested"], equalTo<Any>(true))
- assertThat(templateContext["album"], equalTo<Any>(album))
- assertThat(templateContext["page"], equalTo<Any>("5"))
- }
-
- @Test
- fun `get request with image sets image in template context`() {
- request("", GET)
- val image = mock<Image>()
- addImage("image-id", image)
- addHttpRequestParameter("image", "image-id")
- page.handleRequest(freenetRequest, templateContext)
- assertThat(templateContext["imageRequested"], equalTo<Any>(true))
- assertThat(templateContext["image"], equalTo<Any>(image))
- }
-
- @Test
- fun `get request with sone sets sone in template context`() {
- request("", GET)
- val sone = mock<Sone>()
- addSone("sone-id", sone)
- addHttpRequestParameter("sone", "sone-id")
- page.handleRequest(freenetRequest, templateContext)
- assertThat(templateContext["soneRequested"], equalTo<Any>(true))
- assertThat(templateContext["sone"], equalTo<Any>(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<Any>(true))
- @Suppress("UNCHECKED_CAST")
- assertThat(templateContext["albums"] as Iterable<Album>, 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<Sone>().apply {
- val rootAlbum = mock<Album>()
- val firstAlbum = mock<Album>()
- val firstImage = mock<Image>().run { whenever(isInserted).thenReturn(true); this }
- whenever(firstAlbum.images).thenReturn(listOf(firstImage))
- val secondAlbum = mock<Album>()
- val secondImage = mock<Image>().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<Any>(true))
- assertThat(templateContext["sone"], equalTo<Any>(currentSone))
- }
-
- @Test
- fun `page is link-excepted`() {
- assertThat(page.isLinkExcepted(null), equalTo(true))
- }
-
-}
+++ /dev/null
-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<PostVisibilityFilter>()
- private val page = IndexPage(template, webInterface, postVisibilityFilter)
-
- @Before
- fun setupPostVisibilityFilter() {
- whenever(postVisibilityFilter.isVisible(ArgumentMatchers.eq(currentSone))).thenReturn(object : Predicate<Post> {
- override fun apply(input: Post?) = true
- })
- }
-
- private fun createPost(time: Long, directed: Boolean = false) = mock<Post>().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<Post>, 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<Sone>()
- 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<Post>, 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<Sone>()
- 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<Post>, contains(
- posts[0], followedPosts[0], posts[1], followedPosts[1], posts[2]
- ))
- }
-
-}
+++ /dev/null
-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<Sone>().apply {
- whenever(identity).thenReturn(if (local) mock<OwnIdentity>() else mock<Identity>())
- whenever(isKnown).thenReturn(!new)
- whenever(this.time).thenReturn(time)
- whenever(this.posts).thenReturn((0..(posts - 1)).map { mock<Post>() })
- whenever(this.replies).thenReturn((0..(replies - 1)).map { mock<PostReply>() }.toSet())
- val album = mock<Album>()
- whenever(album.images).thenReturn(((0..(images - 1)).map { mock<Image>() }))
- val rootAlbum = mock<Album>().apply {
- whenever(albums).thenReturn(listOf(album))
- }
- whenever(this.rootAlbum).thenReturn(rootAlbum)
- whenever(this.profile).thenReturn(mock<Profile>())
- whenever(id).thenReturn(name.toLowerCase())
- whenever(this.name).thenReturn(name)
- }
-
- private fun verifySonesAreInOrder(vararg indices: Int) {
- @Suppress("UNCHECKED_CAST")
- assertThat(templateContext["knownSones"] as Iterable<Sone>, 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)
- }
-
-}
+++ /dev/null
-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)
- }
- }
-
-}
+++ /dev/null
-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<Sone>())
- }
- }
-
- @Test
- fun `locking an valid local sone locks the sone and redirects to return page`() {
- addHttpRequestParameter("sone", "sone-id")
- val sone = mock<Sone>()
- addLocalSone("sone-id", sone)
- addHttpRequestParameter("returnPage", "return.html")
- verifyRedirect("return.html") {
- verify(core).lockSone(sone)
- }
- }
-
-}
+++ /dev/null
-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<Sone>().apply {
- whenever(id).thenReturn(hashCode().toString())
- val identity = mock<OwnIdentity>().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<Sone>, 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<Identity>, 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<Sone>, containsInAnyOrder(sones[0], sones[1], sones[2]))
- assertThat(templateContext["identitiesWithoutSone"] as Iterable<Identity>, 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))
- }
-
-}
+++ /dev/null
-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))
- }
-
-}
+++ /dev/null
-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<Post>(), mock<Post>())
- 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<PostReply>(), mock<PostReply>())
- 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<Sone>(), mock<Sone>())
- 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")
- }
-
-}
+++ /dev/null
-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<Any>(true))
- assertThat(templateContext["show-notification-new-sones"], equalTo<Any>(true))
- assertThat(templateContext["show-notification-new-posts"], equalTo<Any>(true))
- assertThat(templateContext["show-notification-new-replies"], equalTo<Any>(true))
- assertThat(templateContext["enable-sone-insert-notifications"], equalTo<Any>(true))
- assertThat(templateContext["load-linked-images"], equalTo<Any>("FOLLOWED"))
- assertThat(templateContext["show-custom-avatars"], equalTo<Any>("FOLLOWED"))
- assertThat(templateContext["insertion-delay"], equalTo<Any>(1))
- assertThat(templateContext["characters-per-post"], equalTo<Any>(50))
- assertThat(templateContext["fcp-full-access-required"], equalTo<Any>(1))
- assertThat(templateContext["images-per-page"], equalTo<Any>(4))
- assertThat(templateContext["fcp-interface-active"], equalTo<Any>(true))
- assertThat(templateContext["require-full-access"], equalTo<Any>(true))
- assertThat(templateContext["negative-trust"], equalTo<Any>(7))
- assertThat(templateContext["positive-trust"], equalTo<Any>(8))
- assertThat(templateContext["post-cut-off-length"], equalTo<Any>(51))
- assertThat(templateContext["posts-per-page"], equalTo<Any>(10))
- assertThat(templateContext["trust-comment"], equalTo<Any>("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 <T> 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 <T> 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 }
- }
-
-}
+++ /dev/null
-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<FreenetRequest>("/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()))
- }
-
-}
+++ /dev/null
-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<SoneRescuer>()
-
- 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<Any>(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()
- }
- }
-
-}
+++ /dev/null
-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<Sone>())
- 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<Sone>())
- 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<Post>())
- 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<Post>())
- 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<PostReply>().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<PostReply>().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<Album>())
- 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<Album>())
- 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<Image>())
- 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<Image>())
- addHttpRequestParameter("query", "image-id")
- verifyRedirect("imageBrowser.html?image=image-id")
- }
-
- private fun createReply(text: String, postId: String? = null, sone: Sone? = null) = mock<PostReply>().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<Post>().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<Sone>().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<Post>(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<Post>(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<Post>(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<Post>(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<Post>(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<Post>(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<Post>(postWithMatch))
- }
-
- private fun createPost(id: String, text: String, recipient: Sone?) = mock<Post>().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<Sone>().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<Post>(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 <T> get(key: String): T? = templateContext[key] as? T
-
-}
+++ /dev/null
-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<Sone>().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<Sone>().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<Any>(value))
-
- private fun <T> verifyVariableMatches(name: String, matcher: Matcher<T>) {
- 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<Sone>(), mock<Sone>())
- 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<Notification>().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))
- }
- }
-
-}
+++ /dev/null
-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<Sone>()
- addSone("sone-id", sone)
- verifyRedirect("return.html") {
- verify(core).trustSone(eq(currentSone), eq(sone))
- }
- }
-
-}
+++ /dev/null
-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<Post>().apply { whenever(isLoaded).thenReturn(true) }
- val loadedPost2 = mock<Post>().apply { whenever(isLoaded).thenReturn(true) }
- val notLoadedPost1 = mock<Post>()
- val notLoadedPost2 = mock<Post>()
- 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<Post>().apply { whenever(isLoaded).thenReturn(true) }
- addPost("post-id", post)
- verifyRedirect("return.html") {
- verify(core).unbookmarkPost(post)
- }
- }
-
-}
+++ /dev/null
-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")
- }
- }
-
-}
+++ /dev/null
-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")
- }
- }
-
-}
+++ /dev/null
-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<Sone>())
- 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<Sone>().apply { whenever(isLocal).thenReturn(true) }
- addLocalSone("local-sone", sone)
- verifyRedirect("return.html") {
- verify(core).unlockSone(sone)
- }
- }
-
-}
+++ /dev/null
-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<Sone>()
- addSone("sone-id", sone)
- verifyRedirect("return.html") {
- verify(core).untrustSone(currentSone, sone)
- }
- }
-
-}
+++ /dev/null
-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<Album>().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<Sone>())
- 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<String>("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<Image.Modifier>()
- val image = mock<Image>().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()
- }
- }
-
-}
+++ /dev/null
-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<Post>()
-
- 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<Any>(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<Any>(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"))
- }
-
-}
+++ /dev/null
-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<Sone>())
- private val foreignPost2 = createPost("foreign-post2", "Second Foreign Post.", 2000, mock<Sone>())
- private val directed1 = createPost("post3", "First directed.", 1500, mock<Sone>(), recipient = currentSone)
- private val directed2 = createPost("post4", "Second directed.", 2500, mock<Sone>(), 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<Any>(""))
- }
-
- @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<Any>("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<Any>(currentSone))
- assertThat(templateContext["soneId"], equalTo<Any>("sone-id"))
- }
-
- private fun createPost(id: String, text: String, time: Long, sender: Sone? = null, recipient: Sone? = null) = mock<Post>().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<Post>, 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<Post>, contains(directed1, post1))
- }
-
- private fun createReply(text: String, time: Long, post: Post?) = mock<PostReply>().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<Post>, 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))
- }
-
-}
--- /dev/null
+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<Any>(version))
+ }
+
+ @Test
+ fun `page sets correct homepage in template context`() {
+ page.processTemplate(freenetRequest, templateContext)
+ assertThat(templateContext["homepage"], equalTo<Any>(homepage))
+ }
+
+ @Test
+ fun `page sets correct year in template context`() {
+ page.processTemplate(freenetRequest, templateContext)
+ assertThat(templateContext["year"], equalTo<Any>(year))
+ }
+
+}
--- /dev/null
+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<Post>()
+ 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())
+ }
+ }
+
+}
--- /dev/null
+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<Post>().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<Post>, contains(post2, post3, post1))
+ assertThat((templateContext["pagination"] as Pagination<Post>).items, contains(post2, post3, post1))
+ assertThat(templateContext["postsNotLoaded"], equalTo<Any>(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<Post>, contains(post2, post1))
+ assertThat((templateContext["pagination"] as Pagination<Post>).items, contains(post2, post1))
+ assertThat(templateContext["postsNotLoaded"], equalTo<Any>(true))
+ }
+
+}
--- /dev/null
+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<Any>(true))
+ }
+
+ private fun createAlbum(albumId: String) = deepMock<Album>().apply {
+ whenever(id).thenReturn(albumId)
+ selfMock<Modifier>().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")
+ }
+ }
+
+}
--- /dev/null
+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<Any>("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<Any>(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<Sone>()
+ 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<Sone>()
+ 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")
+ }
+ }
+
+}
--- /dev/null
+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<Post>().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<Post>().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<Sone>().apply { addLocalSone("sender-id", this) }
+ val post = mock<Post>().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<Any>(true))
+ assertThat(templateContext["returnPage"], equalTo<Any>("return.html"))
+ assertThat(templateContext["postId"], equalTo<Any>("post-id"))
+ assertThat(templateContext["text"], equalTo<Any>(""))
+ }
+
+ @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<Any>("return.html"))
+ assertThat(templateContext["postId"], equalTo<Any>("post-id"))
+ assertThat(templateContext["text"], equalTo<Any>("new text"))
+ }
+
+}
--- /dev/null
+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<Sone>().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<OwnIdentity>().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<String>(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<Sone>, 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<OwnIdentity>, contains(ownIdentities_[1], ownIdentities_[2]))
+ }
+
+ @Test
+ fun `sone is created and logged in`() {
+ addExistingOwnIdentities()
+ request("", POST)
+ addHttpRequestParameter("identity", "own-id-3")
+ val newSone = mock<Sone>()
+ 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<Any>(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))
+ }
+
+}
--- /dev/null
+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<Sone>()
+ private val album = mock<Album>()
+ private val parentAlbum = mock<Album>()
+
+ 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<Album>()
+ addAlbum("album-id", album)
+ addHttpRequestParameter("album", "album-id")
+ page.processTemplate(freenetRequest, templateContext)
+ assertThat(templateContext["album"], equalTo<Any>(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<Album>())
+ addAlbum("album-id", album)
+ addHttpRequestParameter("album", "album-id")
+ verifyRedirect("imageBrowser.html?album=parent-id") {
+ verify(core).deleteAlbum(album)
+ }
+ }
+
+}
--- /dev/null
+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<Image>()
+ private val sone = mock<Sone>()
+
+ override fun getPage() = page
+
+ @Before
+ fun setupImage() {
+ val album = mock<Album>()
+ 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<Any>(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)
+ }
+ }
+
+}
--- /dev/null
+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<Post>()
+ private val sone = mock<Sone>()
+
+ 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<Any>(post))
+ assertThat(templateContext["returnPage"], equalTo<Any>("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<Any>(post))
+ assertThat(templateContext["returnPage"], equalTo<Any>("return.html"))
+ }
+
+}
--- /dev/null
+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<Any>(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
+ }
+ }
+
+}
--- /dev/null
+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<Sone>()
+ private val reply = mock<PostReply>()
+
+ 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<Any>("reply-id"))
+ assertThat(templateContext["returnPage"], equalTo<Any>("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<Any>("reply-id"))
+ assertThat(templateContext["returnPage"], equalTo<Any>("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)
+ }
+ }
+
+}
--- /dev/null
+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)
+ }
+ }
+
+}
--- /dev/null
+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<Notification>()
+
+ 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()
+ }
+ }
+
+}
--- /dev/null
+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<Sone>()
+ addSone("remote-sone-id", remoteSone)
+ addHttpRequestParameter("returnPage", "return.html")
+ addHttpRequestParameter("sone", "remote-sone-id")
+ verifyRedirect("return.html") {
+ verify(core).distrustSone(currentSone, remoteSone)
+ }
+ }
+
+}
--- /dev/null
+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<Album>()
+ private val parentAlbum = mock<Album>()
+ private val modifier = mockBuilder<Modifier>()
+ private val sone = mock<Sone>()
+
+ 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()
+ }
+ }
+
+}
--- /dev/null
+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<Image>()
+ private val modifier = mockBuilder<Modifier>()
+ private val sone = mock<Sone>()
+ private val album = mock<Album>()
+
+ 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()
+ }
+ }
+
+}
--- /dev/null
+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<Any>(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<Any>(true))
+ }
+
+}
--- /dev/null
+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<Image>()
+ 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<Any>("First"))
+ assertThat(templateContext["middleName"], equalTo<Any>("Middle"))
+ assertThat(templateContext["lastName"], equalTo<Any>("Last"))
+ assertThat(templateContext["birthDay"], equalTo<Any>(31))
+ assertThat(templateContext["birthMonth"], equalTo<Any>(12))
+ assertThat(templateContext["birthYear"], equalTo<Any>(1999))
+ assertThat(templateContext["avatarId"], equalTo<Any>("image-id"))
+ assertThat(templateContext["fields"], equalTo<Any>(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<Any>("First"))
+ assertThat(templateContext["middleName"], equalTo<Any>("Middle"))
+ assertThat(templateContext["lastName"], equalTo<Any>("Last"))
+ assertThat(templateContext["birthDay"], equalTo<Any>(31))
+ assertThat(templateContext["birthMonth"], equalTo<Any>(12))
+ assertThat(templateContext["birthYear"], equalTo<Any>(1999))
+ assertThat(templateContext["avatarId"], equalTo<Any>("image-id"))
+ assertThat(templateContext["fields"], equalTo<Any>(listOf(firstField, secondField)))
+ }
+
+ private fun <T> 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<Image>()
+ 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<Any>("new-field"))
+ assertThat(templateContext["duplicateFieldName"], equalTo<Any>(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}")
+ }
+
+}
--- /dev/null
+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<Sone>()
+ 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<Sone>()
+ addSone("sone-id1", firstSone)
+ val secondSone = mock<Sone>()
+ 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<Sone>())
+ }
+ }
+
+}
--- /dev/null
+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)))
+ }
+
+}
--- /dev/null
+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<Album>()
+ addAlbum("album-id", album)
+ addHttpRequestParameter("album", "album-id")
+ addHttpRequestParameter("page", "5")
+ page.handleRequest(freenetRequest, templateContext)
+ assertThat(templateContext["albumRequested"], equalTo<Any>(true))
+ assertThat(templateContext["album"], equalTo<Any>(album))
+ assertThat(templateContext["page"], equalTo<Any>("5"))
+ }
+
+ @Test
+ fun `get request with image sets image in template context`() {
+ request("", GET)
+ val image = mock<Image>()
+ addImage("image-id", image)
+ addHttpRequestParameter("image", "image-id")
+ page.handleRequest(freenetRequest, templateContext)
+ assertThat(templateContext["imageRequested"], equalTo<Any>(true))
+ assertThat(templateContext["image"], equalTo<Any>(image))
+ }
+
+ @Test
+ fun `get request with sone sets sone in template context`() {
+ request("", GET)
+ val sone = mock<Sone>()
+ addSone("sone-id", sone)
+ addHttpRequestParameter("sone", "sone-id")
+ page.handleRequest(freenetRequest, templateContext)
+ assertThat(templateContext["soneRequested"], equalTo<Any>(true))
+ assertThat(templateContext["sone"], equalTo<Any>(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<Any>(true))
+ @Suppress("UNCHECKED_CAST")
+ assertThat(templateContext["albums"] as Iterable<Album>, 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<Sone>().apply {
+ val rootAlbum = mock<Album>()
+ val firstAlbum = mock<Album>()
+ val firstImage = mock<Image>().run { whenever(isInserted).thenReturn(true); this }
+ whenever(firstAlbum.images).thenReturn(listOf(firstImage))
+ val secondAlbum = mock<Album>()
+ val secondImage = mock<Image>().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<Any>(true))
+ assertThat(templateContext["sone"], equalTo<Any>(currentSone))
+ }
+
+ @Test
+ fun `page is link-excepted`() {
+ assertThat(page.isLinkExcepted(null), equalTo(true))
+ }
+
+}
--- /dev/null
+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<PostVisibilityFilter>()
+ private val page = IndexPage(template, webInterface, postVisibilityFilter)
+
+ @Before
+ fun setupPostVisibilityFilter() {
+ whenever(postVisibilityFilter.isVisible(ArgumentMatchers.eq(currentSone))).thenReturn(object : Predicate<Post> {
+ override fun apply(input: Post?) = true
+ })
+ }
+
+ private fun createPost(time: Long, directed: Boolean = false) = mock<Post>().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<Post>, 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<Sone>()
+ 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<Post>, 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<Sone>()
+ 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<Post>, contains(
+ posts[0], followedPosts[0], posts[1], followedPosts[1], posts[2]
+ ))
+ }
+
+}
--- /dev/null
+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<Sone>().apply {
+ whenever(identity).thenReturn(if (local) mock<OwnIdentity>() else mock<Identity>())
+ whenever(isKnown).thenReturn(!new)
+ whenever(this.time).thenReturn(time)
+ whenever(this.posts).thenReturn((0..(posts - 1)).map { mock<Post>() })
+ whenever(this.replies).thenReturn((0..(replies - 1)).map { mock<PostReply>() }.toSet())
+ val album = mock<Album>()
+ whenever(album.images).thenReturn(((0..(images - 1)).map { mock<Image>() }))
+ val rootAlbum = mock<Album>().apply {
+ whenever(albums).thenReturn(listOf(album))
+ }
+ whenever(this.rootAlbum).thenReturn(rootAlbum)
+ whenever(this.profile).thenReturn(mock<Profile>())
+ whenever(id).thenReturn(name.toLowerCase())
+ whenever(this.name).thenReturn(name)
+ }
+
+ private fun verifySonesAreInOrder(vararg indices: Int) {
+ @Suppress("UNCHECKED_CAST")
+ assertThat(templateContext["knownSones"] as Iterable<Sone>, 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)
+ }
+
+}
--- /dev/null
+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)
+ }
+ }
+
+}
--- /dev/null
+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<Sone>())
+ }
+ }
+
+ @Test
+ fun `locking an valid local sone locks the sone and redirects to return page`() {
+ addHttpRequestParameter("sone", "sone-id")
+ val sone = mock<Sone>()
+ addLocalSone("sone-id", sone)
+ addHttpRequestParameter("returnPage", "return.html")
+ verifyRedirect("return.html") {
+ verify(core).lockSone(sone)
+ }
+ }
+
+}
--- /dev/null
+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<Sone>().apply {
+ whenever(id).thenReturn(hashCode().toString())
+ val identity = mock<OwnIdentity>().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<Sone>, 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<Identity>, 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<Sone>, containsInAnyOrder(sones[0], sones[1], sones[2]))
+ assertThat(templateContext["identitiesWithoutSone"] as Iterable<Identity>, 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))
+ }
+
+}
--- /dev/null
+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))
+ }
+
+}
--- /dev/null
+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<Post>(), mock<Post>())
+ 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<PostReply>(), mock<PostReply>())
+ 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<Sone>(), mock<Sone>())
+ 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")
+ }
+
+}
--- /dev/null
+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<Any>(true))
+ assertThat(templateContext["show-notification-new-sones"], equalTo<Any>(true))
+ assertThat(templateContext["show-notification-new-posts"], equalTo<Any>(true))
+ assertThat(templateContext["show-notification-new-replies"], equalTo<Any>(true))
+ assertThat(templateContext["enable-sone-insert-notifications"], equalTo<Any>(true))
+ assertThat(templateContext["load-linked-images"], equalTo<Any>("FOLLOWED"))
+ assertThat(templateContext["show-custom-avatars"], equalTo<Any>("FOLLOWED"))
+ assertThat(templateContext["insertion-delay"], equalTo<Any>(1))
+ assertThat(templateContext["characters-per-post"], equalTo<Any>(50))
+ assertThat(templateContext["fcp-full-access-required"], equalTo<Any>(1))
+ assertThat(templateContext["images-per-page"], equalTo<Any>(4))
+ assertThat(templateContext["fcp-interface-active"], equalTo<Any>(true))
+ assertThat(templateContext["require-full-access"], equalTo<Any>(true))
+ assertThat(templateContext["negative-trust"], equalTo<Any>(7))
+ assertThat(templateContext["positive-trust"], equalTo<Any>(8))
+ assertThat(templateContext["post-cut-off-length"], equalTo<Any>(51))
+ assertThat(templateContext["posts-per-page"], equalTo<Any>(10))
+ assertThat(templateContext["trust-comment"], equalTo<Any>("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 <T> 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 <T> 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 }
+ }
+
+}
--- /dev/null
+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<FreenetRequest>("/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()))
+ }
+
+}
--- /dev/null
+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<SoneRescuer>()
+
+ 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<Any>(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()
+ }
+ }
+
+}
--- /dev/null
+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<Sone>())
+ 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<Sone>())
+ 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<Post>())
+ 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<Post>())
+ 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<PostReply>().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<PostReply>().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<Album>())
+ 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<Album>())
+ 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<Image>())
+ 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<Image>())
+ addHttpRequestParameter("query", "image-id")
+ verifyRedirect("imageBrowser.html?image=image-id")
+ }
+
+ private fun createReply(text: String, postId: String? = null, sone: Sone? = null) = mock<PostReply>().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<Post>().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<Sone>().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<Post>(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<Post>(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<Post>(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<Post>(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<Post>(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<Post>(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<Post>(postWithMatch))
+ }
+
+ private fun createPost(id: String, text: String, recipient: Sone?) = mock<Post>().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<Sone>().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<Post>(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 <T> get(key: String): T? = templateContext[key] as? T
+
+}
--- /dev/null
+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<Sone>().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<Sone>().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<Any>(value))
+
+ private fun <T> verifyVariableMatches(name: String, matcher: Matcher<T>) {
+ 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<Sone>(), mock<Sone>())
+ 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<Notification>().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))
+ }
+ }
+
+}
--- /dev/null
+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<Sone>()
+ addSone("sone-id", sone)
+ verifyRedirect("return.html") {
+ verify(core).trustSone(eq(currentSone), eq(sone))
+ }
+ }
+
+}
--- /dev/null
+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<Post>().apply { whenever(isLoaded).thenReturn(true) }
+ val loadedPost2 = mock<Post>().apply { whenever(isLoaded).thenReturn(true) }
+ val notLoadedPost1 = mock<Post>()
+ val notLoadedPost2 = mock<Post>()
+ 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<Post>().apply { whenever(isLoaded).thenReturn(true) }
+ addPost("post-id", post)
+ verifyRedirect("return.html") {
+ verify(core).unbookmarkPost(post)
+ }
+ }
+
+}
--- /dev/null
+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")
+ }
+ }
+
+}
--- /dev/null
+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")
+ }
+ }
+
+}
--- /dev/null
+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<Sone>())
+ 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<Sone>().apply { whenever(isLocal).thenReturn(true) }
+ addLocalSone("local-sone", sone)
+ verifyRedirect("return.html") {
+ verify(core).unlockSone(sone)
+ }
+ }
+
+}
--- /dev/null
+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<Sone>()
+ addSone("sone-id", sone)
+ verifyRedirect("return.html") {
+ verify(core).untrustSone(currentSone, sone)
+ }
+ }
+
+}
--- /dev/null
+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<Album>().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<Sone>())
+ 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<String>("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<Modifier>()
+ val image = mock<Image>().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()
+ }
+ }
+
+}
--- /dev/null
+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<Post>()
+
+ 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<Any>(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<Any>(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"))
+ }
+
+}
--- /dev/null
+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<Sone>())
+ private val foreignPost2 = createPost("foreign-post2", "Second Foreign Post.", 2000, mock<Sone>())
+ private val directed1 = createPost("post3", "First directed.", 1500, mock<Sone>(), recipient = currentSone)
+ private val directed2 = createPost("post4", "Second directed.", 2500, mock<Sone>(), 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<Any>(""))
+ }
+
+ @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<Any>("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<Any>(currentSone))
+ assertThat(templateContext["soneId"], equalTo<Any>("sone-id"))
+ }
+
+ private fun createPost(id: String, text: String, time: Long, sender: Sone? = null, recipient: Sone? = null) = mock<Post>().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<Post>, 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<Post>, contains(directed1, post1))
+ }
+
+ private fun createReply(text: String, time: Long, post: Post?) = mock<PostReply>().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<Post>, 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))
+ }
+
+}