/* * Copyright (C) 2008-2019, Juick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package com.juick.server.tests; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.gargoylesoftware.htmlunit.CookieManager; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.css.StyleElement; import com.gargoylesoftware.htmlunit.html.DomElement; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.github.scribejava.apis.AppleClientSecretGenerator; import com.jayway.jsonpath.JsonPath; import com.juick.*; import com.juick.model.Tag; import com.juick.model.*; import com.juick.server.MockDeleteListener; import com.juick.server.MockNotificationListener; import com.juick.server.MockUpdateListener; import com.juick.service.*; import com.juick.service.activities.DeleteUserEvent; import com.juick.service.activities.UpdateEvent; import com.juick.service.component.SystemEvent; import com.juick.test.util.MockUtils; import com.juick.util.*; import com.juick.util.formatters.PlainTextFormatter; import com.juick.www.WebApp; import com.juick.www.ad.models.Site; import com.juick.www.api.SystemActivity; import com.juick.www.api.Users; import com.juick.www.api.activity.Profile; import com.juick.www.api.activity.helpers.ProfileUriBuilder; import com.juick.www.api.activity.model.Context; import com.juick.www.api.activity.model.activities.*; import com.juick.www.api.activity.model.objects.Actor; import com.juick.www.api.activity.model.objects.Application; import com.juick.www.api.activity.model.objects.Note; import com.juick.www.api.activity.model.objects.Person; import com.juick.www.api.webfinger.model.Account; import com.juick.www.api.xnodeinfo2.model.NodeInfo; import com.overzealous.remark.Remark; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.pebbletemplates.pebble.PebbleEngine; import io.pebbletemplates.pebble.error.PebbleException; import io.pebbletemplates.pebble.template.PebbleTemplate; import jakarta.servlet.http.Cookie; import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Marshaller; import jakarta.xml.bind.Unmarshaller; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.text.StringEscapeUtils; import org.junit.jupiter.api.*; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.*; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.DigestUtils; import org.springframework.util.FileSystemUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.tomitribe.auth.signatures.Base64; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.xml.sax.SAXException; import rocks.xmpp.addr.Jid; import rocks.xmpp.core.session.Extension; import rocks.xmpp.core.session.XmppSession; import rocks.xmpp.core.session.XmppSessionConfiguration; import ru.sape.SapePageLinks; import javax.inject.Inject; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.spec.InvalidKeySpecException; import java.sql.Timestamp; import java.time.Instant; import java.time.OffsetDateTime; import java.util.*; import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.StreamSupport; import static com.juick.www.api.activity.model.Context.ACTIVITY_MEDIA_TYPE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.util.AssertionErrors.assertNotEquals; import static org.springframework.test.web.client.ExpectedCount.times; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * Created by vitalyster on 25.11.2016. */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @TestPropertySource(properties = {"ios_app_id=12345678.com.juick.ExampleApp"}) @AutoConfigureMockMvc @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ServerTests { @Inject private MockMvc mockMvc; @Inject private WebClient webClient; @Inject private TestRestTemplate restTemplate; @Inject private MessagesService messagesService; @Inject private UserService userService; @Inject private TagService tagService; @Inject private ObjectMapper jsonMapper; @Inject private CommandsManager commandsManager; @Inject private SubscriptionService subscriptionService; @Inject private PrivacyQueriesService privacyQueriesService; @Inject private JdbcTemplate jdbcTemplate; @Inject private EmailService emailService; @Inject private ChatService chatService; @Inject private TelegramService telegramService; @Inject private ServerManager serverManager; @Inject private KeystoreManager keystoreManager; @Inject private StorageService storageService; @Inject private PebbleEngine pebbleEngine; @Value("${ios_app_id:}") private String appId; @Inject private SignatureManager signatureManager; @Inject private ActivityPubManager activityPubManager; @Inject private ProfileUriBuilder profileUriBuilder; @Inject private WebApp webApp; @Inject private RestTemplate apClient; @Inject private Profile profileController; @Value("classpath:snapshots/activity/testuser.json") private Resource testuserResponse; @Value("classpath:snapshots/activity/testapp.json") private Resource testappResponse; @Value("classpath:snapshots/activity/testfollow.json") private Resource testfollowRequest; @Value("classpath:snapshots/activity/testdelete.json") private Resource testDeleteRequest; @Value("classpath:snapshots/activity/test_suspended_user.json") private Resource testSuspendedUserResponse; @Value("classpath:snapshots/email/subscription.html") private Resource testSubscriptionHtmlEmail; @Value("classpath:snapshots/email/private.html") private Resource testPrivateHtmlEmail; @Value("classpath:snapshots/email/subscription.txt") private Resource testSubscriptionTextEmail; @Value("classpath:static/av-96.png") private Resource defaultAvatar; @Value("classpath:cmyk.jpg") private Resource cmykJpeg; @Value("classpath:nojfif.jpg") private Resource nojfif; @Value("classpath:hubzilla_activity.json") private Resource hubzillaActivity; @Value("classpath:hubzilla_follow.json") private Resource hubzillaFollow; @Value("classpath:to_as_string.json") private Resource honkFollow; @Value("classpath:announce.json") private Resource noteWithDocument; @Value("classpath:note_with_attachment.json") private Resource noteWithAttachment; @Value("classpath:2936611-57.jpg") private Resource jpegNoJfifTiff; @Value("classpath:Transparent.gif") private Resource invisiblePixel; @Value("classpath:sape.xml") private Resource sapeOutput; @Value("classpath:flag.json") private Resource flagPayload; @Value("classpath:friendica_update.json") private Resource friendicaUpdate; @Inject AppleClientSecretGenerator clientSecretGenerator; @Inject private Remark remarkConverter; @Inject private KeystoreManager testKeystoreManager; @Inject private ApplicationEventPublisher applicationEventPublisher; @Inject private Users usersController; @Inject private User serviceUser; @Inject private User archiveUser; private static User ugnich, freefd; static String ugnichName, ugnichPassword, freefdName, freefdPassword; URI emptyUri = URI.create(StringUtils.EMPTY); private static boolean isSetUp = false; @BeforeEach public void setUp() throws Exception { String imgDir = storageService.getImageDirectory(); FileSystemUtils.deleteRecursively(Paths.get(imgDir)); Files.createDirectories(Paths.get(imgDir)); Files.createDirectory(Paths.get(imgDir, "p")); Files.createDirectory(Paths.get(imgDir, "photos-1024")); Files.createDirectory(Paths.get(imgDir, "photos-512")); Files.createDirectory(Paths.get(imgDir, "ps")); Files.createDirectory(Paths.get(imgDir, "a")); Files.createDirectory(Paths.get(imgDir, "ao")); Files.createDirectory(Paths.get(imgDir, "as")); if (!isSetUp) { ugnichName = "ugnich"; ugnichPassword = "secret"; freefdName = "freefd"; freefdPassword = "MyPassw0rd!"; ugnich = userService.createUser(ugnichName, ugnichPassword) .orElseThrow(IllegalStateException::new); freefd = userService.createUser(freefdName, freefdPassword) .orElseThrow(IllegalStateException::new); webClient.getOptions().setJavaScriptEnabled(false); webClient.getOptions().setCssEnabled(false); isSetUp = true; } MockitoAnnotations.openMocks(this); } @AfterEach public void teardown() throws IOException { String imgDir = storageService.getImageDirectory(); try { FileSystemUtils.deleteRecursively(Paths.get(imgDir)); } catch (FileSystemException e) { // Skip Windows exceptions when files are in use } } @Test public void getMyFeed() { jdbcTemplate.execute("DELETE FROM telegram"); jdbcTemplate.execute("DELETE FROM subscr_users"); subscriptionService.subscribeUser(freefd, ugnich); int mid0 = messagesService.createMessage(ugnich.getUid(), "test", null, Set.of()); int mid2 = messagesService.createMessage(ugnich.getUid(), "test2", null, Set.of()); List freefdFeed = messagesService.getMyFeed(freefd.getUid(), 0, false); assertThat(freefdFeed.get(0), equalTo(mid2)); User tonya = userService.createUser("Tonya", "secret").orElseThrow(IllegalStateException::new); int mid3 = messagesService.createMessage(tonya.getUid(), "test3", null, Set.of()); messagesService.recommendMessage(mid3, ugnich.getUid()); assertThat(messagesService.getMyFeed(freefd.getUid(), 0, false).get(0), equalTo(mid2)); assertThat(messagesService.getMyFeed(freefd.getUid(), 0, true).get(0), equalTo(mid3)); assertThat(messagesService.getMyFeed(freefd.getUid(), mid2, true).get(0), equalTo(mid0)); assertThat(messagesService.recommendMessage(mid0, ugnich.getUid()), equalTo(MessagesService.RecommendStatus.Added)); assertThat(messagesService.getMessage(mid0).orElseThrow(IllegalStateException::new).getRecommendations() .size(), equalTo(1)); assertThat(messagesService.recommendMessage(mid0, ugnich.getUid()), equalTo(MessagesService.RecommendStatus.Deleted)); assertThat(messagesService.getMessage(mid0).get().getRecommendations().size(), equalTo(0)); assertThat(messagesService.getAll(ugnich.getUid(), 0).get(0), equalTo(mid3)); Tag yoTag = tagService.getTag("yoyo", true); assertThat(tagService.getTag("YOYO", false), equalTo(yoTag)); int mid = messagesService.createMessage(ugnich.getUid(), "yo", null, Set.of(yoTag)); Message msg = messagesService.getMessage(mid).get(); List subscribers = subscriptionService.getSubscribedUsers(ugnich.getUid(), msg); telegramService.createTelegramUser(12345, "freefd"); String loginhash = jdbcTemplate.queryForObject("SELECT loginhash FROM telegram where tg_id=?", String.class, 12345); userService.setTelegramUser(loginhash, freefd.getUid()); List telegramSubscribers = telegramService.getTelegramIdentifiers(subscribers); assertThat(subscribers.size(), equalTo(1)); assertThat(subscribers.size(), equalTo(telegramSubscribers.size())); assertThat(subscribers.get(0).getUid(), equalTo(freefd.getUid())); tagService.blacklistTag(freefd, yoTag); List subscribers2 = subscriptionService.getSubscribedUsers(ugnich.getUid(), msg); assertThat(subscribers2.size(), equalTo(0)); assertThat(telegramService.getTelegramIdentifiers(subscribers2).size(), equalTo(0)); tagService.blacklistTag(freefd, yoTag); assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), msg).size(), equalTo(1)); subscriptionService.unSubscribeUser(freefd, ugnich); assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), msg).size(), equalTo(0)); Message mentionMessage = new Message(); mentionMessage.setUser(ugnich); mentionMessage.setText("@freefd - dick"); assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), mentionMessage).size(), equalTo(1)); subscriptionService.subscribeUser(freefd, ugnich); assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), mentionMessage).size(), equalTo(1)); } @Test public void pmTests() { chatService.createMessage(freefd.getUid(), ugnich.getUid(), "hello"); Message pm = chatService.getChat(ugnich.getUid(), freefd.getUid()).get(0); assertThat(pm.getText(), equalTo("hello")); assertThat(pm.getUser().getUid(), equalTo(freefd.getUid())); } @Test public void messageTests() { User user = userService.createUser("mmmme", "secret").orElseThrow(IllegalStateException::new); assertEquals("mmmme", user.getName()); int mid = messagesService.createMessage(user.getUid(), "yo", null, Set.of()); Message msg = messagesService.getMessage(mid).get(); assertEquals("yo", msg.getText()); User me = msg.getUser(); assertEquals("mmmme", me.getName()); assertEquals("mmmme", messagesService.getMessageAuthor(mid).getName()); int tagID = tagService.createTag("weather"); Tag tag = tagService.getTag(tagID); Set tagList = Set.of(tag); int mid2 = messagesService.createMessage(user.getUid(), "yo2", null, tagList); Message msg2 = messagesService.getMessage(mid2).get(); assertEquals(1, msg2.getTags().size()); Exception exc = assertThrows(UsernameTakenException.class, () -> { userService.createUser("ugnicH", "x"); }); assertEquals("Username taken", exc.getMessage()); User hugnich = userService.createUser("hugnich", "x").orElseThrow(IllegalStateException::new); int rid = messagesService.createReply(msg2.getMid(), 0, hugnich, "bla-bla", null); assertEquals(1, rid); assertThat(msg2.getTo(), equalTo(AnonymousUser.INSTANCE)); Message reply = messagesService.getReply(msg2.getMid(), rid); assertThat(reply.getTo().getName(), equalTo(user.getName())); List replies = messagesService.getReplies(user, msg2.getMid()); assertThat(replies.size(), equalTo(1)); assertThat(replies.get(0), equalTo(reply)); int ridToReply = messagesService.createReply(msg2.getMid(), 1, hugnich, "blax2", null); Message reply2 = messagesService.getReply(msg2.getMid(), ridToReply); assertThat(reply.getTo().getName(), equalTo(user.getName())); List replies2 = messagesService.getReplies(user, msg2.getMid()); assertThat(replies2.size(), equalTo(2)); assertThat(replies2.get(1), equalTo(reply2)); Message msg3 = messagesService.getMessage(mid2).get(); assertEquals(2, msg3.getReplies()); assertEquals("weather", msg3.getTags().stream().findFirst().get().getName()); assertThat(hugnich, is(userService.checkPassword(hugnich.getName(), "x").get())); assertThat(userService.checkPassword(hugnich.getName(), "xy"), is(Optional.empty())); subscriptionService.subscribeMessage(msg, user); subscriptionService.subscribeMessage(msg, ugnich); int reply_id = messagesService.createReply(msg.getMid(), 0, ugnich, "comment", null); assertEquals(1, subscriptionService .getUsersSubscribedToComments(msg, messagesService.getReply(msg.getMid(), reply_id)) .size()); assertThat(messagesService.getDiscussions(ugnich.getUid(), 0L).get(0), equalTo(msg.getMid())); messagesService.deleteMessage(user.getUid(), mid); messagesService.deleteMessage(user.getUid(), mid2); String htmlTagName = ">_<"; Tag htmlTag = tagService.getTag(htmlTagName, true); TagStats htmlTagStats = new TagStats(); htmlTagStats.setTag(htmlTag); String dbTagName = jdbcTemplate.queryForObject("select name from tags where name=?", String.class, htmlTagName); assertEquals(dbTagName, htmlTag.getName()); int mid4 = messagesService.createMessage(user.getUid(), "yoyoyo", null, Set.of()); Message msg4 = messagesService.getMessage(mid4).get(); assertEquals(StringUtils.EMPTY, MessageUtils.getTagsString(msg4)); messagesService.deleteMessage(user.getUid(), mid4); } @Test public void tagParsingTests() { assertEquals(0, tagService.fromString("*").getRight().size()); assertEquals(1, tagService.fromString("*kek").getRight().size()); assertEquals(1, tagService.fromString("*123").getRight().size()); assertEquals(0, tagService.fromString("#").getRight().size()); assertEquals(1, tagService.fromString("#kek").getRight().size()); assertEquals(1, tagService.fromString("#12k").getRight().size()); assertEquals(0, tagService.fromString("#123").getRight().size()); assertEquals(2, tagService.fromString("#mixed *tags #123 test").getRight().size()); } @Test public void likeTypeStatsTests() { User dsdss = userService.createUser("dsdss", "secret").orElseThrow(IllegalStateException::new); final int freefdId = freefd.getUid(); int mid = messagesService.createMessage(dsdss.getUid(), "yo", null, Set.of()); messagesService.likeMessage(mid, freefdId, 2); messagesService.likeMessage(mid, freefdId, 2); messagesService.likeMessage(mid, freefdId, 3); messagesService.likeMessage(mid, freefdId, 1); Message msg4 = messagesService.getMessage(mid).get(); assertThat(msg4.getRecommendations().size(), equalTo(1)); assertEquals(2, msg4.getReactions().stream().filter(r -> r.getId() == 2).findFirst() .orElseThrow(IllegalStateException::new).getCount()); assertEquals(1, msg4.getReactions().stream().filter(r -> r.getId() == 3).findFirst() .orElseThrow(IllegalStateException::new).getCount()); } @Test public void lastJidShouldNotBeDeleted() { User hugnich2 = userService.createUser("hugnich2", "x").orElseThrow(IllegalStateException::new); jdbcTemplate.update("INSERT INTO jids(user_id,jid,active) VALUES(?,?,?)", hugnich2.getUid(), "firstjid@localhost", 1); jdbcTemplate.update("INSERT INTO jids(user_id,jid,active) VALUES(?,?,?)", hugnich2.getUid(), "secondjid@localhost", 1); assertThat(userService.deleteJID(hugnich2.getUid(), "secondjid@localhost"), equalTo(true)); assertThat(userService.deleteJID(hugnich2.getUid(), "firstjid@localhost"), equalTo(false)); } @Test public void lastEmailShouldNotBeDeleted() { User hugnich3 = userService.createUser("hugnich3", "x").orElseThrow(IllegalStateException::new); jdbcTemplate.update("INSERT INTO emails(user_id,email) VALUES(?,?)", hugnich3.getUid(), "first@localhost"); jdbcTemplate.update("INSERT INTO emails(user_id,email) VALUES(?,?)", hugnich3.getUid(), "second@localhost"); emailService.addEmail(hugnich3.getUid(), "test@email.example.com"); assertThat(emailService.getNotificationsEmail(hugnich3.getUid()), is("test@email.example.com")); emailService.disableEmail(hugnich3, "test@email.example.com"); assertThat(emailService.getNotificationsEmail(hugnich3.getUid()), is(emptyString())); assertThat(emailService.deleteEmail(hugnich3.getUid(), "test@email.example.com"), equalTo(true)); assertThat(emailService.deleteEmail(hugnich3.getUid(), "second@localhost"), equalTo(true)); assertThat(emailService.deleteEmail(hugnich3.getUid(), "first@localhost"), equalTo(false)); } @Test public void messageUpdatedTimeShouldMatchLastReplyTime() throws InterruptedException { User hugnich4 = userService.createUser("hugnich4", "x").orElseThrow(IllegalStateException::new); int mid = messagesService.createMessage(hugnich4.getUid(), "yo", null, Set.of()); Instant ts = jdbcTemplate .queryForObject("SELECT updated FROM messages WHERE message_id=?", Timestamp.class, mid) .toInstant(); Thread.sleep(1000); int rid = messagesService.createReply(mid, 0, ugnich, "people", null); Instant rts = jdbcTemplate .queryForObject("SELECT updated FROM messages WHERE message_id=?", Timestamp.class, mid) .toInstant(); assertThat(rts, greaterThan(ts)); Message msg = messagesService.getReply(mid, rid); assertThat(rts, equalTo(msg.getCreated())); messagesService.deleteMessage(hugnich4.getUid(), mid); } @Test public void testAllUnAuthorized() throws Exception { mockMvc.perform(get("/api/")).andExpect(status().isMovedPermanently()); mockMvc.perform(get("/api/auth")).andExpect(status().isUnauthorized()) .andExpect(header().exists("WwW-Authenticate")); mockMvc.perform(get("/api/home")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/api/messages/recommended")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/api/messages/set_privacy")).andExpect(status().isUnauthorized()); } @Test public void homeTestWithMessages() throws Exception { String msgText = "Привет, я - Угнич"; CommandResult result = commandsManager.processCommand(ugnich, msgText, URI.create("https://static.juick.com/settings/facebook.png")); int mid = result.getNewMessage().get().getMid(); Message msg = messagesService.getMessage(mid).get(); tagService.createTag("тест"); String hash = DigestUtils.md5DigestAsHex(IOUtils.toByteArray(defaultAvatar.getInputStream())); mockMvc.perform(get("/api/home").with(httpBasic(ugnichName, ugnichPassword))).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$[0].mid", is(msg.getMid()))) .andExpect(jsonPath("$[0].timestamp", is(DateFormattersHolder.getMessageFormatterInstance() .format(msg.getCreated())))) .andExpect(jsonPath("$[0].body", is(msg.getText()))) .andExpect(jsonPath("$[0].attachment.url", is(String.format("https://i.juick.com/p/%d.png", msg.getMid())))) .andExpect(jsonPath("$[0].attachment.small.url", is(String.format("https://i.juick.com/photos-512/%d.png", msg.getMid())))) .andExpect(jsonPath("$[0].user.avatar", is(String.format("http://localhost:8080/av-96-%s.png", hash)))); } @Test public void homeTestWithMessagesAndRememberMe() throws Exception { String ugnichHash = userService.getHashByUID(ugnich.getUid()); mockMvc.perform(get("/api/home").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isOk()); mockMvc.perform(get("/api/home").param("hash", ugnichHash)).andExpect(status().isOk()); mockMvc.perform(get("/api/home").header("Authorization", String.format("Juick %s", ugnichHash))) .andExpect(status().isOk()); } @Test public void homeTestWithMessagesAndSimpleCors() throws Exception { mockMvc.perform( get("/api/home").with(httpBasic(ugnichName, ugnichPassword)).header("Origin", "http://api.example.net")) .andExpect(status().isOk()) .andExpect(header().string("Access-Control-Allow-Origin", "*")); mockMvc.perform(get("/u/ugnich").header("Origin", "http://api.example.net")).andExpect(status().isOk()) .andExpect(header().string("Access-Control-Allow-Origin", "*")); } @Test public void homeTestWithPreflightCors() throws Exception { mockMvc.perform(options("/api/home").with(httpBasic(ugnichName, ugnichPassword)) .header("Origin", "http://api.example.net") .header("Access-Control-Request-Method", "POST") .header("Access-Control-Request-Headers", "X-PINGOTHER, Content-Type")) .andExpect(status().isOk()) .andExpect(header().string("Access-Control-Allow-Origin", "*")) .andExpect(header().string("Access-Control-Allow-Methods", "POST,GET,PUT,OPTIONS,DELETE")) .andExpect(header().string("Access-Control-Allow-Headers", "X-PINGOTHER, Content-Type")); } @Test public void anonymousApis() throws Exception { mockMvc.perform(get("/api/messages")).andExpect(status().isOk()); mockMvc.perform(get("/api/users").param("uname", "ugnich").param("uname", "freefd")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$", hasSize(2))); } @Test public void messagesUrlTest() throws Exception { User dsds4345 = userService.createUser("dsds4345", "secret").orElseThrow(IllegalStateException::new); String freefdHash = userService.getHashByUID(freefd.getUid()); String userIdHash = userService.getHashByUID(dsds4345.getUid()); final int freefdId = freefd.getUid(); int mid = messagesService.createMessage(dsds4345.getUid(), "yo", null, Set.of()); messagesService.likeMessage(mid, freefdId, 2); messagesService.likeMessage(mid, freefdId, 2); messagesService.likeMessage(mid, freefdId, 3); mockMvc.perform(get("/api/messages?" + "hash=" + userIdHash)).andDo(print()).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect((jsonPath("$[0].reactions[?(@.id == 3)].count", is(Collections.singletonList(1))))) .andExpect((jsonPath("$[0].reactions[?(@.id == 2)].count", is(Collections.singletonList(2))))); mockMvc.perform(get("/api/reactions?hash=" + userIdHash)).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.length()", is(7))); } @Test public void tags() throws Exception { Tag weather = tagService.getTag("weather", true); Tag yo = tagService.getTag("yo", true); messagesService.createMessage(ugnich.getUid(), "text", null, Set.of(yo, weather)); messagesService.createMessage(freefd.getUid(), "text2", null, Set.of(yo)); MvcResult result = mockMvc.perform(get("/api/tags")).andExpect(status().isOk()).andReturn(); List tagsFromApi = jsonMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); TagStats yoStats = tagsFromApi.stream().filter(t -> t.getTag().getName().equals("yo")).findFirst() .get(); assertThat(yoStats.getUsageCount(), is(2)); MvcResult result2 = mockMvc.perform(get("/api/tags").param("user_id", String.valueOf(ugnich.getUid()))) .andExpect(status().isOk()).andReturn(); List ugnichTagsFromApi = jsonMapper.readValue(result2.getResponse().getContentAsString(), new TypeReference<>() { }); TagStats yoUgnichStats = ugnichTagsFromApi.stream().filter(t -> t.getTag().getName().equals("yo")) .findFirst() .get(); assertThat(yoUgnichStats.getUsageCount(), is(1)); } @Test public void postWithReferer() throws Exception { mockMvc.perform(post("/api/post").param("body", "yo").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isOk()); } @Test public void threadWithEphemeralNumberShouldReturn404() throws Exception { mockMvc.perform(get("/api/thread").param("mid", "999999999") .with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().is4xxClientError()); } @Test public void performRequestsWithIssuedToken() throws Exception { String ugnichHash = userService.getHashByUID(ugnich.getUid()); mockMvc.perform(get("/api/home")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/api/auth")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/api/auth").with(httpBasic(ugnichName, "wrongpassword"))) .andExpect(status().isUnauthorized()); MvcResult result = mockMvc.perform(get("/api/auth").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isOk()).andReturn(); String authHash = result.getResponse().getContentAsString(); assertThat(authHash, equalTo(ugnichHash)); mockMvc.perform(get("/api/home").param("hash", ugnichHash)).andExpect(status().isOk()); } @Test public void registerForNotificationsTests() throws Exception { String token = "123456"; ExternalToken registration = new ExternalToken(null, "apns", token, null); mockMvc.perform(put("/api/notifications").with(httpBasic(ugnichName, ugnichPassword)) .contentType(MediaType.APPLICATION_JSON) .content(jsonMapper.writeValueAsBytes(Collections.singletonList(registration)))) .andExpect(status().isOk()); MvcResult result = mockMvc .perform(get("/api/notifications").param("uid", String.valueOf(ugnich.getUid())) .with(httpBasic(serviceUser.getName(), "password"))) .andExpect(status().isOk()).andReturn(); List users = jsonMapper.readValue(result.getResponse().getContentAsString(), new TypeReference<>() { }); assertThat(users.size(), is(1)); assertThat(users.get(0).getTokens().size(), is(1)); assertThat(users.get(0).getTokens().get(0).token(), equalTo(token)); } @Test public void tg2juickLinks() { UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://juick.com/m/123456#23") .build(); assertThat(uriComponents.getPath().substring(3), is("123456")); assertThat(uriComponents.getFragment(), is("23")); } @Test public void notificationsTokensTest() throws Exception { List tokens = Collections.singletonList(new ExternalToken(null, "gcm", "123456", null)); mockMvc.perform(delete("/api/notifications").with(httpBasic(ugnichName, ugnichPassword)) .contentType(MediaType.APPLICATION_JSON).content(jsonMapper.writeValueAsBytes(tokens))) .andExpect(status().isForbidden()); mockMvc.perform(delete("/api/notifications").with(httpBasic(serviceUser.getName(), "password")) .contentType(MediaType.APPLICATION_JSON).content(jsonMapper.writeValueAsBytes(tokens))) .andExpect(status().isOk()); } @Test public void notificationsSettingsAllowedOnlyForServiceUser() throws Exception { CommandResult result = commandsManager.processCommand(ugnich, "yo", emptyUri); String stringValueOfMid = String.valueOf(result.getNewMessage().get().getMid()); mockMvc.perform(get("/api/notifications").with(httpBasic(serviceUser.getName(), "password")) .param("mid", stringValueOfMid).param("uid", String.valueOf(ugnich.getUid()))) .andExpect(status().isOk()); mockMvc.perform( get("/api/notifications").param("mid", stringValueOfMid).param("uid", String.valueOf(ugnich.getUid()))) .andExpect(status().isUnauthorized()); } @Test public void topTest() { int topmid = messagesService.createMessage(ugnich.getUid(), "top message", null, Set.of()); IntStream.rangeClosed(6, 12).forEach(i -> { User next = new User(); next.setUid(i); messagesService.createReply(topmid, 0, next, "yo", null); }); List topCandidates = messagesService.getPopularCandidates(); assertThat(topCandidates.size(), is(1)); assertThat(topCandidates.get(0), is(topmid)); Tag juickTag = tagService.getTag("juick", false); assertThat(juickTag.TID, is(2)); tagService.updateTags(topmid, Collections.singletonList(juickTag)); assertThat(messagesService.getPopularCandidates().isEmpty(), is(true)); tagService.updateTags(topmid, Collections.singletonList(juickTag)); assertThat(messagesService.getPopularCandidates().isEmpty(), is(false)); CommandResult resultRecommend = commandsManager.commandRecommend(serviceUser, URI.create(""), String.valueOf(topmid)); List msgs = messagesService.getUserRecommendations(serviceUser.getUid(), 0); assertThat(msgs.get(0), is(topmid)); List allMsgs = messagesService.getUserBlogWithRecommendations(serviceUser, ugnich, 0, 0); assertThat(allMsgs.contains(topmid), is(true)); CommandResult resultUndo = commandsManager.commandRecommend(serviceUser, URI.create(""), String.valueOf(topmid)); assertThat(messagesService.getPopularCandidates().isEmpty(), is(false)); jdbcTemplate.update("INSERT INTO tags(tag_id, name) VALUES(805, 'NSFW')"); Tag nsfw = tagService.getTag("NSFW", false); assertThat(nsfw.TID, equalTo(805)); tagService.updateTags(topmid, Collections.singletonList(nsfw)); assertThat(messagesService.getPopularCandidates().isEmpty(), is(true)); User recommender = userService.createUser("recommender2", "x").orElseThrow(IllegalStateException::new); int anotherMid = messagesService.createMessage(ugnich.getUid(), "top2", null, Set.of()); messagesService.recommendMessage(anotherMid, freefd.getUid()); messagesService.recommendMessage(anotherMid, recommender.getUid()); assertThat(messagesService.getPopularCandidates().isEmpty(), is(true)); User recommender3 = userService.createUser("recommender3", "x").orElseThrow(IllegalStateException::new); messagesService.recommendMessage(anotherMid, recommender3.getUid()); assertThat(messagesService.getPopularCandidates().get(0), is(anotherMid)); messagesService.recommendMessage(anotherMid, serviceUser.getUid()); assertThat(messagesService.getPopularCandidates().isEmpty(), is(true)); } @Test public void inReplyToScannerTest() { String header = "<123456.56@juick.com>"; Scanner headerScanner = new Scanner(header).useDelimiter(EmailManager.MSGID_PATTERN); int mid = Integer.parseInt(headerScanner.next()); int rid = Integer.parseInt(headerScanner.next()); headerScanner.close(); assertThat(mid, equalTo(123456)); assertThat(rid, equalTo(56)); } @Test public void lastMessagesTest() throws Exception { mockMvc.perform(get("/rss/")).andExpect(status().isOk()) .andExpect(content().contentType("application/rss+xml;charset=UTF-8")) .andExpect(xpath("/rss/channel/description").string("The latest messages at Juick")); } @Test public void botCommandsTests() throws Exception { assertThat(commandsManager.processCommand(AnonymousUser.INSTANCE, "PING", emptyUri).getText(), is("PONG")); // subscription commands have two lines, others have 1 assertThat( commandsManager.processCommand(AnonymousUser.INSTANCE, "help", emptyUri).getText() .split("\n").length, is(32)); } @Test public void protocolTests() throws Exception { String tmpDir = storageService.getTemporaryDirectory(); User user = userService.createUser("me", "secret").orElseThrow(IllegalStateException::new); Tag yo = tagService.getTag("yo", true); Message msg = commandsManager .processCommand(user, "*yo yoyo", URI.create("https://static.juick.com/settings/facebook.png")) .getNewMessage().get(); assertThat(msg.getAttachmentType(), is("png")); Message msgreply = commandsManager .processCommand(user, "#" + msg.getMid() + " yyy", HttpUtils .downloadImage(URI.create("https://static.juick.com/settings/xmpp.png") .toURL(), tmpDir)) .getNewMessage().get(); assertThat(msgreply.getAttachmentType(), equalTo("png")); assertEquals("yoyo", messagesService.getMessage(msg.getMid()).get().getText()); assertEquals("yo", tagService.getMessageTags(msg.getMid()).get(0).getTag().getName()); CommandResult yoyoMsg = commandsManager.processCommand(user, "*yo", URI.create("https://static.juick.com/settings/facebook.png")); assertThat(yoyoMsg.getNewMessage().isPresent(), is(true)); assertThat(yoyoMsg.getNewMessage().get().getTags().stream().findFirst().get(), is(yo)); Message msg2 = yoyoMsg.getNewMessage().get(); int mid = msg2.getMid(); var last = jdbcTemplate.queryForObject("SELECT lastmessage FROM users WHERE id=?", OffsetDateTime.class, user.getUid()); assertThat(last.toInstant(), equalTo(yoyoMsg.getNewMessage().get().getCreated())); assertEquals(true, commandsManager.processCommand(user, String.format("#%d", mid), emptyUri).getText() .startsWith("@me")); User readerUser = userService.createUser("dummyReader", "dummySecret") .orElseThrow(IllegalStateException::new); assertThat( commandsManager.processCommand(readerUser, "s", emptyUri).getText() .startsWith("You are subscribed to"), is(true)); assertThat( commandsManager.processCommand(readerUser, "S", emptyUri).getText() .startsWith("You are subscribed to"), is(true)); assertEquals("Subscribed", commandsManager.processCommand(readerUser, "S #" + mid, emptyUri).getText()); assertEquals("Message is added to your recommendations", commandsManager.processCommand(readerUser, "! #" + mid, emptyUri).getText()); int rid = messagesService.createReply(mid, 0, user, "comment", null); assertEquals(1, subscriptionService .getUsersSubscribedToComments(messagesService.getMessage(mid).get(), messagesService.getReply(mid, rid)) .size()); privacyQueriesService.blacklistUser(user, readerUser); assertEquals(0, subscriptionService .getUsersSubscribedToComments(messagesService.getMessage(mid).get(), messagesService.getReply(mid, rid)) .size()); assertEquals(1, subscriptionService.getUsersSubscribedToComments(messagesService.getMessage(mid).get(), messagesService.getReply(mid, rid), true).size()); assertEquals("Subscribed to @" + user.getName(), commandsManager.processCommand(readerUser, "S @" + user.getName(), emptyUri).getText()); List friends = userService.getUserFriends(readerUser.getUid()); assertEquals(2, friends.size()); assertEquals(1, userService.getUserReaders(user.getUid()).size()); String expectedSecondReply = "Reply posted.\n#" + mid + "/2 " + "https://juick.com/m/" + mid + "#2"; String expectedThirdReply = "Reply posted.\n#" + mid + "/3 " + "https://juick.com/m/" + mid + "#3"; assertEquals(expectedSecondReply, commandsManager .processCommand(user, "#" + mid + " yoyo", URI.create("https://static.juick.com/settings/facebook.png")) .getText()); assertEquals(expectedThirdReply, commandsManager.processCommand(user, " \t\n #" + mid + "/2 ", URI.create("https://static.juick.com/settings/facebook.png")).getText()); Message reply = messagesService.getReplies(user, mid).stream().filter(m -> m.getRid() == 3).findFirst() .orElse(new Message()); var lastreply = jdbcTemplate.queryForObject("SELECT lastmessage FROM users WHERE id=?", OffsetDateTime.class, user.getUid()); assertThat(lastreply.toInstant(), equalTo(reply.getCreated())); assertEquals(2, reply.getReplyto()); assertThat(commandsManager.processCommand(readerUser, "#" + mid + " *yo *there", emptyUri).getText(), startsWith("Reply posted")); assertEquals("Tags are updated", commandsManager.processCommand(user, "#" + mid + " *there", emptyUri).getText()); assertEquals(2, tagService.getMessageTags(mid).size()); assertThat(messagesService.getMessage(mid).get().getTags().size(), is(2)); assertEquals("Tag added to your blacklist", commandsManager.processCommand(readerUser, "BL *there", emptyUri).getText()); assertEquals(0, subscriptionService.getSubscribedUsers(user.getUid(), msg2).size()); assertEquals("Tags are updated", commandsManager.processCommand(user, "#" + mid + " *there", emptyUri).getText()); assertEquals(1, tagService.getMessageTags(mid).size()); User taggerUser = userService.createUser("dummyTagger", "dummySecret") .orElseThrow(IllegalStateException::new); assertEquals("Subscribed", commandsManager.processCommand(taggerUser, "S *yo", emptyUri).getText()); assertEquals(2, subscriptionService.getSubscribedUsers(user.getUid(), msg2).size()); assertEquals("Unsubscribed from yo", commandsManager.processCommand(taggerUser, "U *yo", emptyUri).getText()); assertEquals(1, subscriptionService.getSubscribedUsers(user.getUid(), msg2).size()); assertEquals(1, userService.getUserReaders(user.getUid()).size()); String readerFeed = commandsManager.processCommand(readerUser, "#", emptyUri).getText(); assertThat(readerFeed.startsWith("Your feed"), is(true)); assertEquals("Unsubscribed from @" + user.getName(), commandsManager.processCommand(readerUser, "U @" + user.getName(), emptyUri).getText()); assertEquals(0, userService.getUserReaders(user.getUid()).size()); assertEquals(1, userService.getUserFriends(user.getUid()).size()); assertEquals("Unsubscribed from #" + mid, commandsManager.processCommand(readerUser, "u #" + mid, emptyUri).getText()); assertEquals(0, subscriptionService .getUsersSubscribedToComments(messagesService.getMessage(mid).get(), messagesService.getReply(mid, rid)) .size()); assertNotEquals("should NOT be deleted", String.format("Message %s deleted", mid), commandsManager.processCommand(readerUser, "D #" + mid, emptyUri).getText()); assertEquals("Message deleted", commandsManager.processCommand(user, "D #" + mid, emptyUri).getText()); assertEquals("Message not found", commandsManager.processCommand(user, "#" + mid, emptyUri).getText()); String expectedCodeMessage = "some smelly code goes here\n" + "> void main(void** args) {\n" + "> }"; String codeAndTags = "*code\n" + expectedCodeMessage; Message codeAndTagsMessage = commandsManager.processCommand(user, codeAndTags, emptyUri).getNewMessage() .get(); Set codeAndTagsTags = codeAndTagsMessage.getTags(); assertEquals(1, codeAndTagsTags.size()); assertEquals("code", codeAndTagsTags.stream().findFirst().get().getName()); assertEquals(expectedCodeMessage, codeAndTagsMessage.getText()); CommandResult result = commandsManager.processCommand(user, "*one *two *three *four *five *six test", emptyUri); assertThat(result.getNewMessage(), is(Optional.empty())); assertThat(result.getText(), is("Sorry, 5 tags maximum.")); result = commandsManager.processCommand(user, String.format("#%d *one *two *three *four *five *six", msg.getMid()), emptyUri); assertThat(result.getNewMessage(), is(Optional.empty())); assertThat(result.getText(), is("Tags are NOT updated (5 tags maximum?)")); result = commandsManager.processCommand(user, "I'm very smart to post my login url there" + "", emptyUri); assertThat(result.getNewMessage().isPresent(), is(true)); assertFalse(result.getNewMessage().get().getText().contains("VTYZkKV8FWkmu6g1")); result = commandsManager.processCommand(user, "*корм *juick_ppl *рационализм *? *мюсли а сколько микроморт в дневной порции сверхмюслей?", emptyUri); assertThat(result.getNewMessage().isPresent(), is(true)); String tags = "*Juick *Google *Google Play"; String data = "Вчера отправлял *NSFW постинг в топ :)"; result = commandsManager.processCommand(user, String.format("%s %s", tags, data), emptyUri); assertThat(result.getNewMessage().get().getTags().size(), equalTo(3)); assertThat(result.getNewMessage().get().getText(), equalTo(data)); tags = "*\u041a\u0438\u0435\u0432 *\u044d\u043a\u043e\u043b\u043e\u0433\u0438\u044f"; data = "* \u043c\u0443\u0441\u043e\u0440\\n\u0423 \u043c\u0435\u043d\u044f \u043a\u0430\u0436\u0434\u0443\u044e \u043d\u0435\u0434\u0435\u043b\u044e \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043f\u043e 4-5 \u0431\u0443\u0442\u044b\u043b\u043e\u043a 1,5\u043b \u041f\u0415\u0422. \u041c\u043d\u0435 \u0433\u0435\u043c\u043e\u0440\u043d\u043e \u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u043f\u043e \u043a\u0438\u043b\u043e\u0433\u0440\u0430\u043c\u043c\u0443 \u0438\u043b\u0438 \u043f\u043e 5\u043a\u0433 \u044d\u0442\u043e\u0433\u043e \u043c\u0443\u0441\u043e\u0440\u0430, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0437\u0442\u0438 \u0435\u0433\u043e \u0435\u0449\u0435 \u043a\u0443\u0434\u0430-\u0442\u043e.\\n\u041d\u0435, \u043d\u0443 \u0435\u0441\u0442\u044c \u043b\u044e\u0434\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442 \u0432\u0442\u043e\u0440\u0441\u044b\u0440\u044c\u0435 \u043f\u043e \u043c\u0443\u0441\u043e\u0440\u043a\u0430\u043c, \u0441\u0432\u0430\u043b\u043a\u0430\u043c, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0434\u0435\u043d\u044c\u0433\u0438 \u043d\u0443\u0436\u043d\u044b. \u0418 \u0431\u044b\u0432\u0430\u044e\u0442 \u0441\u0442\u043e\u044f\u0442 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u044b-\u043a\u043b\u0435\u0442\u043a\u0438 \u0434\u043b\u044f \u043f\u043b\u0430\u0441\u0442\u0438\u043a\u0430, \u043d\u043e \u0442\u0430\u043a \u043a\u0430\u043a \u043c\u0443\u0441\u043e\u0440 \u0432\u044b\u0432\u043e\u0437\u044f\u0442 \u043d\u0435 \u0447\u0430\u0441\u0442\u043e \u0438\u043b\u0438 \u043b\u044e\u0434\u0438 \u0432\u043d\u0435\u0437\u0430\u043f\u043d\u043e \u0432\u044b\u043a\u0438\u0434\u044b\u0432\u0430\u044e\u0442 \u043c\u043d\u043e\u0433\u043e \u043c\u0443\u0441\u043e\u0440\u0430, \u0442\u043e \u0432 \u0442\u043e\u0439 \u043a\u043b\u0435\u0442\u043a\u0435 \u0442\u043e\u0442 \u043c\u0443\u0441\u043e\u0440, \u0447\u0442\u043e \u043d\u0435 \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043b\u0441\u044f \u0432 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u0445."; result = commandsManager.processCommand(user, String.format("%s %s", tags, data), emptyUri); assertThat(result.getNewMessage().get().getTags().size(), equalTo(2)); assertThat(result.getNewMessage().get().getTags().stream().findFirst().get().getName(), equalTo("Киев")); assertThat(result.getNewMessage().get().getText(), equalTo(data)); result = commandsManager.processCommand(user, "S @unknown-user", emptyUri); assertThat(result.getNewMessage(), is(Optional.empty())); assertThat(result.getText(), is("User not found")); } @Test public void mailParserTest() throws Exception { emailService.addEmail(ugnich.getUid(), "ugnich@example.com"); int mid = messagesService.createMessage(ugnich.getUid(), "text", StringUtils.EMPTY, Set.of()); String mail = String.format( "MIME-Version: 1.0\n" + "Received: by 10.176.0.242 with HTTP; Fri, 16 Mar 2018 05:31:50 -0700 (PDT)\n" + "In-Reply-To: <%d.0@juick.com>\n" + "References: <%d.0@juick.com>\n" + "Date: Fri, 16 Mar 2018 15:31:50 +0300\n" + "Delivered-To: ugnich@example.com\n" + "Message-ID: \n" + "Subject: Re: New reply to TJ\n" + "From: Ugnich \n" + "To: Juick \n" + "Content-Type: multipart/alternative; boundary=\"001a11454886e42be5056786ca70\"\n" + "\n" + "--001a11454886e42be5056786ca70\n" + "Content-Type: text/plain; charset=\"UTF-8\"\n" + "\n" + "s2313334\n" + "\n" + "--001a11454886e42be5056786ca70\n" + "Content-Type: text/html; charset=\"UTF-8\"\n" + "\n" + "
s2313334
\n" + "\n" + "--001a11454886e42be5056786ca70--", mid, mid); mockMvc.perform(post("/api/mail").with(httpBasic(serviceUser.getName(), "password")).content(mail)) .andExpect(status().isOk()); String reply = "Return-Path: \n" + "Received: from [192.168.88.140] ([91.244.168.38])\n" + " by smtp.gmail.com with ESMTPSA id r84sm3970197lja.54.2019.06.20.08.39.54\n" + " for \n" + " (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\n" + " Thu, 20 Jun 2019 08:39:54 -0700 (PDT)\n" + "From: Ugnich \n" + "Content-Type: text/plain; charset=utf-8\n" + "Content-Transfer-Encoding: base64\n" + "Mime-Version: 1.0 (1.0)\n" + "Date: Thu, 20 Jun 2019 18:39:54 +0300\n" + "Subject: Re: New reply to vt\n" + "Message-Id: <40BC3538-0A0C-4BD0-8F11-5408A85CC6EF@gmail.com>\n" + "References: <2945559.7@juick.com>\n" + "In-Reply-To: \n" + "To: juick@juick.com\n" + "X-Mailer: iPhone Mail (16F203)\n" + "\n" + "0J3RgyDRjdGC0L4g0L/QvtC60LAhINCU0L7Qu9Cz0L4g0LvQuCwg0YPQvNC10Y7Rh9C4IQ=="; mockMvc.perform(post("/api/mail").with(httpBasic(serviceUser.getName(), "password")).content(reply)) .andExpect(status().isOk()); } @Test @Transactional public void recommendTests() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "to be liked", null, Set.of()); String freefdHash = userService.getHashByUID(freefd.getUid()); int freefdMid = messagesService.createMessage(freefd.getUid(), "to be not liked", null, Set.of()); mockMvc.perform(post("/api/like?mid=" + mid + "&hash=" + freefdHash)).andExpect(status().isOk()) .andExpect(jsonPath("$.status", is("Message is added to your recommendations"))); mockMvc.perform(get("/api/thread?mid=" + mid + "&hash=" + freefdHash)).andExpect(status().isOk()) .andExpect(jsonPath("$[0].recommendations.length()", is(1))) .andExpect(jsonPath("$[0].recommendations[0].uname", is(freefdName))); mockMvc.perform(post("/api/like?mid=" + freefdMid + "&hash=" + freefdHash)) .andExpect(status().isForbidden()); messagesService.createReply(mid, 0, freefd, "reply", null); mockMvc.perform(get("/api/thread?mid=" + mid + "&hash=" + freefdHash)).andExpect(status().isOk()) .andExpect(jsonPath("$.length()", is(2))) .andExpect(jsonPath("$[0].replies", is(1))); mockMvc.perform(get("/api/thread?mid=" + mid + "&hash=" + freefdHash + "&showReplies=false")) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()", is(1))) .andExpect(jsonPath("$[0].replies", is(1))); } @Test public void likesTests() throws Exception { User dsds = userService.createUser("dsds", "secret").orElseThrow(IllegalStateException::new); String freefdHash = userService.getHashByUID(freefd.getUid()); int mid1 = messagesService.createMessage(dsds.getUid(), "yo", null, Set.of()); mockMvc.perform(post("/api/react?mid=" + mid1 + "&hash=" + freefdHash + "&reactionId=2")) .andExpect(status().isOk()); Message msg4 = messagesService.getMessage(mid1).get(); assertThat(msg4.getRecommendations().size(), is(0)); assertThat( messagesService.getMessages(AnonymousUser.INSTANCE, Collections.singletonList(mid1)) .get(0).getRecommendations().size(), is(0)); assertEquals(1, msg4.getReactions().stream().filter(r -> r.getId() == 2).findFirst() .orElseThrow(IllegalStateException::new).getCount()); mockMvc.perform(post("/api/react?mid=" + mid1 + "&hash=" + freefdHash + "&reactionId=1")) .andExpect(status().isOk()); mockMvc.perform(post("/api/react?mid=" + mid1 + "&hash=" + freefdHash + "&reactionId=1")) .andExpect(status().isOk()); assertThat(messagesService.getMessage(mid1).get().getRecommendations().size(), is(1)); } @Test @Order(5) public void lastReadTests() throws Exception { jdbcTemplate.execute("DELETE FROM bl_users"); jdbcTemplate.execute("DELETE FROM messages"); assertThat(userService.isInBLAny(ugnich.getUid(), freefd.getUid()), is(false)); int mid = messagesService.createMessage(ugnich.getUid(), "to be watched", null, Set.of()); subscriptionService.subscribeMessage(messagesService.getMessage(mid).get(), ugnich); messagesService.createReply(mid, 0, freefd, "new reply", null); BiFunction lastRead = (user, m) -> jdbcTemplate.queryForObject( "SELECT last_read_rid FROM subscr_messages WHERE suser_id=? AND message_id=?", Integer.class, user.getUid(), m); assertThat(lastRead.apply(ugnich, mid), is(0)); assertThat(messagesService.getUnread(ugnich).size(), is(1)); assertThat(messagesService.getUnread(ugnich).get(0), is(mid)); assertThat(messagesService.getMessages(ugnich, Collections.singletonList(mid)).get(0).isUnread(), is(true)); messagesService.getReplies(ugnich, mid); assertThat(lastRead.apply(ugnich, mid), is(1)); assertThat(messagesService.getUnread(ugnich).size(), is(0)); messagesService.setLastReadComment(ugnich, mid, 0); assertThat(lastRead.apply(ugnich, mid), is(1)); String ugnichHash = userService.getHashByUID(ugnich.getUid()); int freefdrid = messagesService.createReply(mid, 0, freefd, "again", null); mockMvc.perform(get( String.format("/api/thread/mark_read/%d-%d.gif?hash=%s", mid, freefdrid, ugnichHash))) .andExpect(status().isOk()) .andExpect(content().bytes(IOUtils.toByteArray(invisiblePixel.getInputStream()))); assertThat(lastRead.apply(ugnich, mid), is(freefdrid)); privacyQueriesService.blacklistUser(ugnich, freefd); int newfreefdrid = messagesService.createReply(mid, 0, freefd, "from ban", null); serverManager.processSystemEvent(new SystemEvent(this, SystemActivity.message(serviceUser, messagesService.getReply(mid, newfreefdrid), Collections.emptyList()))); assertThat(userService.isReplyToBL(ugnich, messagesService.getReply(mid, newfreefdrid)), is(true)); // TODO: test event listeners correctly Thread.sleep(2000L); assertThat(lastRead.apply(ugnich, mid), is(newfreefdrid)); privacyQueriesService.blacklistUser(ugnich, freefd); newfreefdrid = messagesService.createReply(mid, 0, freefd, "after ban", null); assertThat(lastRead.apply(ugnich, mid), lessThan(newfreefdrid)); mockMvc.perform(get(String.format("/api/thread?mid=%d&hash=%s", mid, ugnichHash))) .andExpect(status().isOk()); assertThat(lastRead.apply(ugnich, mid), is(newfreefdrid)); } @Test public void feedsShouldNotContainMessagesWithBannedTags() { Tag banned = tagService.getTag("banned", true); int mid = messagesService.createMessage(ugnich.getUid(), "yo", "jpg", Set.of(banned)); privacyQueriesService.blacklistTag(freefd, banned); assertThat(messagesService .getMessages(AnonymousUser.INSTANCE, messagesService.getAll(freefd.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned)), is(true)); assertFalse(messagesService .getMessages(AnonymousUser.INSTANCE, messagesService.getAll(ugnich.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); assertThat(messagesService .getMessages(AnonymousUser.INSTANCE, messagesService.getPhotos(freefd.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned)), is(true)); assertFalse(messagesService .getMessages(AnonymousUser.INSTANCE, messagesService.getPhotos(ugnich.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); messagesService.recommendMessage(mid, serviceUser.getUid()); assertThat(messagesService .getMessages(AnonymousUser.INSTANCE, messagesService.getUserBlogWithRecommendations(serviceUser, freefd, 0, 0)) .stream().noneMatch(m -> m.getTags().contains(banned)), is(true)); assertFalse(messagesService .getMessages(AnonymousUser.INSTANCE, messagesService.getUserBlogWithRecommendations(serviceUser, ugnich, 0, 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); assertThat( messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getMyFeed(freefd.getUid(), 0, true)) .stream().noneMatch(m -> m.getTags().contains(banned)), is(true)); User newUser1 = userService.createUser("newUser1", "12345").orElseThrow(IllegalStateException::new); int newMid = messagesService.createMessage(newUser1.getUid(), "people", null, Set.of(banned)); messagesService.recommendMessage(newMid, ugnich.getUid()); assertThat( messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getMyFeed(freefd.getUid(), 0, true)) .stream().noneMatch(m -> m.getTags().contains(banned)), is(true)); tagService.updateTags(newMid, Collections.singletonList(banned)); assertThat(messagesService.getMessage(newMid).get().getTags().size(), is(0)); privacyQueriesService.blacklistUser(freefd, newUser1); assertThat(messagesService.getMyFeed(freefd.getUid(), 0, true).stream().noneMatch(m -> m == newMid), is(true)); } @Test public void tagsShouldBeDeserializedFromXml() throws JAXBException { XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() .extensions(Extension.of(Message.class)).build(); XmppSession xmpp = new XmppSession("juick.com", configuration) { @Override public void connect(Jid from) { } }; String tag = "yo"; String xml = "yoyoyopeople"; Unmarshaller unmarshaller = xmpp.createUnmarshaller(); rocks.xmpp.core.stanza.model.Message xmppMessage = (rocks.xmpp.core.stanza.model.Message) unmarshaller .unmarshal(new StringReader(xml)); Tag xmlTag = (Tag) unmarshaller.unmarshal(new StringReader(tag)); assertThat(xmlTag.getName(), equalTo("yo")); Message juickMessage = xmppMessage.getExtension(Message.class); List tags = new ArrayList<>(juickMessage.getTags()); assertThat(tags.get(0).getName(), equalTo("yo")); } @Test public void messageParserSerializer() throws Exception { String tagsString = "test test" + (char) 0xA0 + "2 test 3"; Set tags = MessageUtils.parseTags(tagsString); List tagList = tags.stream().map(t -> tagService.getTag(t.getName(), true)) .toList(); assertEquals("test", tagList.get(0).getName()); assertEquals("test 3", tagList.get(2).getName()); assertEquals(3, tagList.size()); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap map = new LinkedMultiValueMap<>(); HttpEntity> request = new HttpEntity<>(map, headers); map.add("body", "*test *test 2 *test 3 YO"); map.add("hash", userService.getHashByUID(ugnich.getUid())); ResponseEntity result = restTemplate.postForEntity("/api/post", request, CommandResult.class); assertThat(result.getStatusCode(), is(HttpStatus.OK)); Message msg = result.getBody().getNewMessage().orElseThrow(); Instant currentDate = msg.getCreated(); String jsonMessage = jsonMapper.writeValueAsString(msg); assertEquals(DateFormattersHolder.getMessageFormatterInstance().format(currentDate), JsonPath.read(jsonMessage, "$.timestamp")); JAXBContext context = JAXBContext.newInstance(Message.class); Marshaller m = context.createMarshaller(); StringWriter sw = new StringWriter(); m.marshal(msg, sw); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new ByteArrayInputStream(sw.toString().getBytes(StandardCharsets.UTF_8))); Node juickNode = doc.getDocumentElement(); NamedNodeMap attrs = juickNode.getAttributes(); assertEquals(DateFormattersHolder.getMessageFormatterInstance().format(currentDate), attrs.getNamedItem("ts").getNodeValue()); MvcResult apiResult = mockMvc.perform(get("/api/thread?mid=" + msg.getMid())).andExpect(status().isOk()) .andReturn(); List fromApi = jsonMapper.readValue( apiResult.getResponse().getContentAsString(StandardCharsets.UTF_8), new TypeReference<>() { }); assertThat(fromApi.get(0).getTags(), is(tags)); } @Test public void emptyAuthenticatedPostShouldThrowBadRequest() throws Exception { mockMvc.perform(post("/api/post").with(httpBasic(serviceUser.getName(), "password"))) .andExpect(status().isBadRequest()); } @Test public void attachmentSizeTests() throws IOException { String tmpPng = "tmp.png"; Files.copy(defaultAvatar.getFile().toPath(), Paths.get(storageService.getTemporaryDirectory(), tmpPng), StandardCopyOption.REPLACE_EXISTING); storageService.saveAvatar(tmpPng, serviceUser); Attachment attachment = storageService.getAvatarMetadata(serviceUser); assertThat(attachment.getHeight(), is(96)); assertThat(attachment.getWidth(), is(96)); } @Test public void meContainsAllInfo() throws Exception { jdbcTemplate.update("DELETE FROM subscr_users"); jdbcTemplate.update("DELETE FROM followers"); assertThat(userService.getUserReaders(ugnich.getUid()).size(), is(0)); assertThat(userService.getUserFriends(ugnich.getUid()).size(), is(0)); commandsManager.processCommand(freefd, "S @ugnich", emptyUri); commandsManager.processCommand(ugnich, "S @freefd", emptyUri); assertThat(userService.getUserReaders(ugnich.getUid()).size(), is(1)); String hash = userService.getHashByUID(ugnich.getUid()); mockMvc.perform(get("/api/me").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(jsonPath("$.hash", is(hash))) .andExpect(jsonPath("$.readers.length()", is(1))) .andExpect(jsonPath("$.read.length()", is(1))); } @Test public void feedsShouldNotContainBannedUsers() throws Exception { commandsManager.processCommand(ugnich, "BL @freefd", emptyUri); CommandResult result = commandsManager.processCommand(ugnich, "freefd - dick", emptyUri); int mid = result.getNewMessage().get().getMid(); commandsManager.processCommand(freefd, String.format("#%d ugnich - dick too", mid), emptyUri); commandsManager.processCommand(serviceUser, String.format("#%d/1 ban for a hour!", mid), emptyUri); commandsManager.processCommand(serviceUser, String.format("#%d freefd is here but it is hidden from you", mid), emptyUri); assertThat(messagesService.getMessage(mid).get().getReplies(), is(3)); Message reply = messagesService.getReply(mid, 3); assertThat(userService.isReplyToBL(ugnich, reply), is(false)); List replies = messagesService.getReplies(ugnich, mid); assertThat(replies.size(), is(1)); commandsManager.processCommand(freefd, String.format("#%d/3 hahaha!", mid), emptyUri); assertThat(messagesService.getMessage(mid).get().getReplies(), is(4)); replies = messagesService.getReplies(ugnich, mid); assertThat(replies.size(), is(1)); mockMvc.perform(get("/api/thread").with(httpBasic(ugnichName, ugnichPassword)).param("mid", String.valueOf(mid))) .andExpect(jsonPath("$[0].replies", is(1))); commandsManager.processCommand(serviceUser, String.format("#%d/4 mmm?!", mid), emptyUri); assertThat(messagesService.getMessage(mid).get().getReplies(), is(5)); replies = messagesService.getReplies(ugnich, mid); reply = messagesService.getReply(mid, 5); assertThat(userService.isReplyToBL(ugnich, reply), is(true)); assertThat(replies.size(), is(1)); List msgs = messagesService.getMessages(ugnich, Collections.singletonList(mid)); assertThat(msgs.get(0).getReplies(), is(1)); commandsManager.processCommand(ugnich, "BL @freefd", emptyUri); messagesService.setRead(ugnich, mid); assertThat(messagesService.getReplies(ugnich, mid).size(), is(5)); List nonblmsgs = messagesService.getMessages(ugnich, Collections.singletonList(mid)); assertThat(nonblmsgs.get(0).getReplies(), is(5)); commandsManager.processCommand(ugnich, "BL @freefd", emptyUri); Tag tag = tagService.getTag("linux", true); int freefdMsg = messagesService.createMessage(freefd.getUid(), "sux", null, Set.of(tag)); assertThat(messagesService.getTag(tag.TID, freefd.getUid(), 0, 10).size(), is(1)); assertThat(messagesService.getTag(tag.TID, ugnich.getUid(), 0, 10).size(), is(0)); messagesService.recommendMessage(freefdMsg, serviceUser.getUid()); assertThat(messagesService.getUserBlogWithRecommendations(serviceUser, ugnich, 0, 0) .contains(freefdMsg), is(false)); commandsManager.processCommand(ugnich, "BL @freefd", emptyUri); } @Test public void cmykJpegShouldBeProcessedCorrectly() throws Exception { String imgDir = storageService.getImageDirectory(); CommandResult postJpgCmyk = commandsManager.processCommand(ugnich, "YO", cmykJpeg.getURI()); assertThat(postJpgCmyk.getNewMessage().isPresent(), is(true)); int mid = postJpgCmyk.getNewMessage().get().getMid(); File originalFile = Paths.get(imgDir, "p", String.format("%d.jpg", mid)).toFile(); assertThat(originalFile.exists(), is(true)); File mediumFile = Paths.get(imgDir, "photos-1024", String.format("%d.jpg", mid)).toFile(); assertThat(mediumFile.exists(), is(true)); assertThat(postJpgCmyk.getNewMessage().get().getAttachment().getWidth(), is(2585)); assertThat(postJpgCmyk.getNewMessage().get().getAttachment().getHeight(), is(3335)); assertThat(postJpgCmyk.getNewMessage().get().getAttachment().getMedium().getHeight(), is(1024)); assertThat(postJpgCmyk.getNewMessage().get().getAttachment().getSmall().getHeight(), is(512)); } @Test public void JpegWithoutJfifShouldBeProcessedCorrectly() throws Exception { String imgDir = storageService.getImageDirectory(); CommandResult postJpgCmyk = commandsManager.processCommand(ugnich, "YO", nojfif.getURI()); assertThat(postJpgCmyk.getNewMessage().isPresent(), is(true)); int mid = postJpgCmyk.getNewMessage().get().getMid(); File originalFile = Paths.get(imgDir, "p", String.format("%d.jpg", mid)).toFile(); assertThat(originalFile.exists(), is(true)); File mediumFile = Paths.get(imgDir, "photos-1024", String.format("%d.jpg", mid)).toFile(); assertThat(mediumFile.exists(), is(true)); assertThat(postJpgCmyk.getNewMessage().get().getAttachment().getWidth(), is(3264)); assertThat(postJpgCmyk.getNewMessage().get().getAttachment().getHeight(), is(2448)); assertThat(postJpgCmyk.getNewMessage().get().getAttachment().getMedium().getHeight(), is(768)); assertThat(postJpgCmyk.getNewMessage().get().getAttachment().getSmall().getHeight(), is(384)); } @Test public void JpegFromJuickUriShouldBeProcessedCorrectly() throws Exception { String imgDir = storageService.getImageDirectory(); String tmpDir = storageService.getTemporaryDirectory(); Path tmpFile = Paths.get(tmpDir, "2915104.jpg"); Files.copy(Paths.get(new ClassPathResource("2915104.jpg").getURI()), tmpFile, StandardCopyOption.REPLACE_EXISTING); assertThat(tmpFile.toFile().exists(), is(true)); CommandResult postJpgiPhone = commandsManager.processCommand(ugnich, "YO", URI.create("juick://2915104.jpg")); assertThat(postJpgiPhone.getNewMessage().isPresent(), is(true)); int mid = postJpgiPhone.getNewMessage().get().getMid(); File originalFile = Paths.get(imgDir, "p", String.format("%d.jpg", mid)).toFile(); assertThat(originalFile.exists(), is(true)); File mediumFile = Paths.get(imgDir, "photos-1024", String.format("%d.jpg", mid)).toFile(); assertThat(mediumFile.exists(), is(true)); assertThat(postJpgiPhone.getNewMessage().get().getAttachment().getWidth(), is(1280)); assertThat(postJpgiPhone.getNewMessage().get().getAttachment().getHeight(), is(1280)); assertThat(postJpgiPhone.getNewMessage().get().getAttachment().getMedium().getHeight(), is(1024)); assertThat(postJpgiPhone.getNewMessage().get().getAttachment().getSmall().getHeight(), is(512)); CommandResult postNojfifTiff = commandsManager.processCommand(ugnich, "YO2", jpegNoJfifTiff.getURI()); assertThat(postNojfifTiff.getNewMessage().isPresent(), is(true)); int mid2 = postNojfifTiff.getNewMessage().get().getMid(); File originalFile2 = Paths.get(imgDir, "p", String.format("%d.jpg", mid2)).toFile(); assertThat(originalFile2.exists(), is(true)); File mediumFile2 = Paths.get(imgDir, "photos-1024", String.format("%d.jpg", mid2)).toFile(); assertThat(mediumFile2.exists(), is(true)); } @Test public void changeExtensionWhenReceiveFileWithWrongContentType() throws Exception { String tmpDir = storageService.getTemporaryDirectory(); Path pngOutput = Paths.get(tmpDir, "cmyk.png"); Files.deleteIfExists(pngOutput); Files.copy(Paths.get(cmykJpeg.getURI()), pngOutput); assertThat(pngOutput.toFile().exists(), is(true)); CommandResult postJpgCmyk = commandsManager.processCommand(ugnich, "YO", pngOutput.toUri()); assertThat(postJpgCmyk.getNewMessage().isPresent(), is(true)); assertThat(postJpgCmyk.getNewMessage().get().getAttachmentType(), is("jpg")); CommandResult replyJpgCmyk = commandsManager.processCommand(ugnich, String.format("#%d YO", postJpgCmyk.getNewMessage().get().getMid()), pngOutput.toUri()); assertThat(replyJpgCmyk.getNewMessage().isPresent(), is(true)); assertThat(replyJpgCmyk.getNewMessage().get().getAttachmentType(), is("jpg")); } @MockBean private MockUpdateListener activityListener; @Captor protected ArgumentCaptor updateEventCaptor; @Test public void messageEditingSpec() throws Exception { MvcResult result = mockMvc .perform(post("/api/post").with(httpBasic(ugnichName, ugnichPassword)).param("body", "YO")) .andExpect(status().is2xxSuccessful()).andReturn(); Message original = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class) .getNewMessage().get(); assertThat(original.getText(), equalTo("YO")); assertThat(original.getUpdatedAt(), equalTo(original.getCreated())); // to have updated_at greater than ts Thread.sleep(1000); result = mockMvc .perform(post("/api/update").with(httpBasic(ugnichName, ugnichPassword)) .param("mid", String.valueOf(original.getMid())) .param("body", "PEOPLE")) .andExpect(status().is2xxSuccessful()).andReturn(); Message edited = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class) .getNewMessage().get(); assertThat(edited.getText(), equalTo("PEOPLE")); assertThat(edited.getUpdatedAt(), greaterThan(edited.getCreated())); Mockito.verify(activityListener, Mockito.times(1)).onApplicationEvent(updateEventCaptor.capture()); UpdateEvent updateEvent = updateEventCaptor.getValue(); assertThat(updateEvent.getUser(), is(ugnich)); assertThat(original.getMid(), is(updateEvent.getMessage().getMid())); mockMvc.perform(post("/api/update").with(httpBasic(freefdName, freefdPassword)) .param("mid", String.valueOf(original.getMid())).param("body", "PEOPLE")) .andExpect(status().is(403)); result = mockMvc .perform(post("/api/comment").with(httpBasic(freefdName, freefdPassword)) .param("mid", String.valueOf(original.getMid())).param("body", "HEY")) .andExpect(status().is2xxSuccessful()).andReturn(); CommandResult comment = jsonMapper.readValue(result.getResponse().getContentAsString(), CommandResult.class); assertThat(comment.getNewMessage().get().getText(), is("HEY")); assertThat(comment.getNewMessage().get().getUpdatedAt(), is(comment.getNewMessage().get().getCreated())); // to have updated_at greater than ts Thread.sleep(1000); result = mockMvc .perform(post("/api/update").with(httpBasic(freefdName, freefdPassword)) .param("mid", String.valueOf(comment.getNewMessage().get().getMid())) .param("rid", String.valueOf(comment.getNewMessage().get().getRid())) .param("body", "HEY, JOE")) .andExpect(status().is2xxSuccessful()).andReturn(); Message editedComment = jsonMapper .readValue(result.getResponse().getContentAsString(), CommandResult.class) .getNewMessage().get(); assertThat(editedComment.getText(), is("HEY, JOE")); assertThat(editedComment.getUpdatedAt(), greaterThan(editedComment.getCreated())); jdbcTemplate.update( "UPDATE replies SET updated_at='1990-05-05 00:00:00' WHERE message_id=? AND reply_id=?", editedComment.getMid(), editedComment.getRid()); Message updatedComment = comment.getNewMessage().get(); result = mockMvc .perform(post("/api/update").with(httpBasic(freefdName, freefdPassword)) .param("mid", String.valueOf(updatedComment.getMid())) .param("rid", String.valueOf(updatedComment.getRid())) .param("body", "HEY, JOE AGAIN")) .andExpect(status().isBadRequest()).andReturn(); assertThat(messagesService.deleteReply(ugnich.getUid(), updatedComment.getMid(), updatedComment.getRid()), is(false)); assertThat(messagesService.deleteReply(freefd.getUid(), updatedComment.getMid(), updatedComment.getRid()), is(true)); assertThat(messagesService.getReply(updatedComment.getMid(), updatedComment.getRid()).getUser(), is(archiveUser)); jdbcTemplate.update("UPDATE messages_txt SET updated_at='1990-05-05 00:00:00' WHERE message_id=?", original.getMid()); assertThat(messagesService.deleteMessage(ugnich.getUid(), original.getMid()), is(true)); assertThat(messagesService.getMessageAuthor(original.getMid()), is(archiveUser)); jdbcTemplate.update("UPDATE messages_txt SET updated_at=? WHERE message_id=?", Timestamp.from(Instant.now()), original.getMid()); assertThat(messagesService.deleteMessage(ugnich.getUid(), original.getMid()), is(false)); assertThat(messagesService.deleteMessage(archiveUser.getUid(), original.getMid()), is(true)); } @Test public void subscribersToRecommendations() { User reader = userService.createUser("reader", "123456").orElseThrow(IllegalStateException::new); User recommender = userService.createUser("recommender", "123456") .orElseThrow(IllegalStateException::new); User lateRecommender = userService.createUser("lateRecommender", "123456") .orElseThrow(IllegalStateException::new); User poster = userService.createUser("poster", "123456").orElseThrow(IllegalStateException::new); subscriptionService.subscribeUser(reader, recommender); subscriptionService.subscribeUser(reader, lateRecommender); Tag sampleTag = tagService.getTag("banned", true); int posterMid = messagesService.createMessage(poster.getUid(), "YO", null, Set.of(sampleTag)); messagesService.recommendMessage(posterMid, recommender.getUid()); BiFunction> subscribers = (recommId, msg) -> subscriptionService .getUsersSubscribedToUserRecommendations(recommId, msg); List recommendSubscribers = subscribers.apply(recommender.getUid(), messagesService.getMessage(posterMid).get()); assertThat(recommendSubscribers.size(), is(1)); assertThat(recommendSubscribers.get(0).getUid(), is(reader.getUid())); privacyQueriesService.blacklistUser(reader, poster); assertThat(subscribers.apply(recommender.getUid(), messagesService.getMessage(posterMid).get()).size(), is(0)); privacyQueriesService.blacklistUser(reader, poster); assertThat(subscribers.apply(recommender.getUid(), messagesService.getMessage(posterMid).get()).size(), is(1)); tagService.blacklistTag(reader, sampleTag); assertThat(subscribers.apply(recommender.getUid(), messagesService.getMessage(posterMid).get()).size(), is(0)); tagService.blacklistTag(reader, sampleTag); assertThat(subscribers.apply(recommender.getUid(), messagesService.getMessage(posterMid).get()).size(), is(1)); messagesService.recommendMessage(posterMid, lateRecommender.getUid()); List lateRecommendSubscribers = subscribers.apply(recommender.getUid(), messagesService.getMessage(posterMid).get()); assertThat(lateRecommendSubscribers.size(), is(0)); int readerMid = messagesService.createMessage(reader.getUid(), "PEOPLE", null, Set.of()); messagesService.recommendMessage(readerMid, recommender.getUid()); assertThat(subscribers.apply(recommender.getUid(), messagesService.getMessage(readerMid).get()).size(), is(0)); } @Test public void mentionsInComments() { User poster = userService.createUser("p", "secret").orElseThrow(IllegalStateException::new); User commenter = userService.createUser("cc", "secret").orElseThrow(IllegalStateException::new); User mentioner = userService.createUser("mmm", "secret").orElseThrow(IllegalStateException::new); int mid = messagesService.createMessage(poster.getUid(), "who is dick?", null, Set.of()); Message msg = messagesService.getMessage(mid).get(); int rid = messagesService.createReply(mid, 0, commenter, "@mmm is dick", null); Message reply = messagesService.getReply(mid, rid); assertThat(subscriptionService.getUsersSubscribedToComments(msg, reply).size(), is(1)); subscriptionService.subscribeUser(mentioner, commenter); assertThat(subscriptionService.getUsersSubscribedToComments(msg, reply).size(), is(1)); privacyQueriesService.blacklistUser(mentioner, commenter); assertThat(subscriptionService.getUsersSubscribedToComments(msg, reply).size(), is(0)); } @Test public void mentionsInPosts() { jdbcTemplate.execute("DELETE FROM bl_users"); jdbcTemplate.execute("DELETE FROM followers"); int mid = messagesService.createMessage(ugnich.getUid(), "@freefd is dick", null, Set.of()); Message msg = messagesService.getMessage(mid).get(); assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), msg).get(0), is(freefd)); privacyQueriesService.blacklistUser(freefd, ugnich); assertThat(subscriptionService.getSubscribedUsers(ugnich.getUid(), msg).size(), is(0)); int mid2 = messagesService.createMessage(freefd.getUid(), "@ugnich is dick", null, Set.of()); Message msg2 = messagesService.getMessage(mid2).get(); assertThat(subscriptionService.getSubscribedUsers(freefd.getUid(), msg2).get(0), is(ugnich)); jdbcTemplate.execute("DELETE FROM bl_users"); } @Test public void credentialsShouldNeverBeSerialized() throws Exception { User yyy = userService.createUser("yyy", "xxxx").orElseThrow(IllegalStateException::new); assertThat(yyy.getCredentials(), is("xxxx")); ObjectMapper jsonMapper = new ObjectMapper(); jsonMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); String jsonUser = jsonMapper.writeValueAsString(yyy); Map user = JsonPath.read(jsonUser, "$"); // only uid, name and uri assertThat(user.keySet().size(), is(3)); JAXBContext context = JAXBContext.newInstance(User.class); Marshaller m = context.createMarshaller(); StringWriter sw = new StringWriter(); m.marshal(yyy, sw); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new ByteArrayInputStream(sw.toString().getBytes(StandardCharsets.UTF_8))); Element juickNode = doc.getDocumentElement(); NamedNodeMap attrs = juickNode.getAttributes(); // uid, name, xmlns, xmlns:user assertThat(attrs.getLength(), is(4)); } @Test public void bannedUserBlogandPostShouldReturn404() throws Exception { String userName = "isilmine"; String userPassword = "secret"; String msgText = "автор этого поста был забанен"; User isilmine = userService.createUser(userName, userPassword).orElseThrow(IllegalStateException::new); int mid = messagesService.createMessage(isilmine.getUid(), msgText, null, Set.of()); mockMvc.perform(get(String.format("/api/thread?mid=%d", mid)) .with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isOk()); jdbcTemplate.update("UPDATE users SET banned=1 WHERE id=?", isilmine.getUid()); mockMvc.perform(get(String.format("/api/thread?mid=%d", mid)) .with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isNotFound()); mockMvc.perform(get("/api/messages?uname=isilmine").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isNotFound()); mockMvc.perform(get("/api/info/isilmine").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isNotFound()); mockMvc.perform(get("/api/info/ugnich").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isOk()); } @Test public void emptyPasswordMeansUserIsDisabled() throws Exception { String userName = "oldschooluser"; String userPassword = ""; userService.createUser(userName, userPassword); mockMvc.perform(get("/api/auth").with(httpBasic(userName, userPassword))) .andExpect(status().isUnauthorized()); mockMvc.perform( post("/login") .with(csrf()) .param("username", userName) .param("password", userPassword)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login?error=1")); } @Test public void bannedUserShouldNotBeInRecommendationsList() { jdbcTemplate.execute("DELETE FROM bl_users"); User ermine = userService.createUser("ermine", "secret").orElseThrow(IllegalStateException::new); User monstreek = userService.createUser("monstreek", "secret").orElseThrow(IllegalStateException::new); User pogo = userService.createUser("pogo", "secret").orElseThrow(IllegalStateException::new); User fmap = userService.createUser("fmap", "secret").orElseThrow(IllegalStateException::new); int mid = messagesService.createMessage(monstreek.getUid(), "KURWA", null, Set.of()); assertThat(messagesService.recommendMessage(mid, ermine.getUid()), is(MessagesService.RecommendStatus.Added)); assertThat(messagesService.recommendMessage(mid, fmap.getUid()), is(MessagesService.RecommendStatus.Added)); assertThat(messagesService.recommendMessage(mid, pogo.getUid()), is(MessagesService.RecommendStatus.Added)); jdbcTemplate.update("INSERT INTO favorites(user_id, user_uri, message_id, like_id, ts) " + "values (0, 'http://example.com/u/test', ?, 1, now())", mid); assertThat(messagesService.getMessage(mid).get().getRecommendations().size(), is(4)); assertThat(CollectionUtils.isEqualCollection( messagesService.getMessagesRecommendations(Collections.singletonList(mid)).stream() .map(Pair::getRight).map(User::getName).toList(), Arrays.asList("fmap", "ermine", "pogo", "Anonymous")), is(true)); privacyQueriesService.blacklistUser(userService.getUserByName("monstreek"), userService.getUserByName("pogo")); assertThat(messagesService.getMessage(mid).get().getRecommendations().size(), is(3)); assertThat(CollectionUtils.isEqualCollection( messagesService.getMessagesRecommendations(Collections.singletonList(mid)).stream() .map(Pair::getRight).map(User::getName).toList(), Arrays.asList("fmap", "ermine", "Anonymous")), is(true)); jdbcTemplate.execute("DELETE FROM favorites"); } @Test public void bannedUserShouldNotBeVisibleToOthers() { jdbcTemplate.execute("DELETE FROM messages"); User casualUser = userService.createUser("user", "secret").orElseThrow(IllegalStateException::new); User bannedUser = userService.createUser("banned", "banned").orElseThrow(IllegalStateException::new); jdbcTemplate.update("UPDATE users SET banned=1 WHERE id=?", bannedUser.getUid()); messagesService.createMessage(bannedUser.getUid(), "KURWA", null, Set.of()); assertThat(messagesService.getAll(casualUser.getUid(), 0).size(), is(0)); assertThat(messagesService.getDiscussions(casualUser.getUid(), 0L).size(), is(0)); assertThat(messagesService.getDiscussions(0, 0L).size(), is(0)); assertThat(messagesService.getAll(bannedUser.getUid(), 0).size(), is(1)); int mid = messagesService.createMessage(casualUser.getUid(), "PEACE", null, Set.of()); User banned = userService.getUserByName("banned"); int bannedRid = messagesService.createReply(mid, 0, banned, "KURWA", null); int casualRid = messagesService.createReply(mid, 0, userService.getUserByName("user"), "DOOR", null); assertThat(messagesService.getReplies(AnonymousUser.INSTANCE, mid).size(), is(1)); assertThat( messagesService.getMessages(AnonymousUser.INSTANCE, Collections.singletonList(mid)) .get(0).getReplies(), is(1)); assertThat(messagesService.getReplies(banned, mid).size(), is(2)); assertThat(messagesService.getMessages(banned, Collections.singletonList(mid)).get(0).getReplies(), is(2)); } @Test public void accountUrlShouldBeExposedOverWebfinger() throws Exception { mockMvc.perform(get("/.well-known/webfinger?resource=acct:ugnich@localhost")).andExpect(status().isOk()) .andExpect(jsonPath("$.subject", is("acct:ugnich@localhost"))) .andExpect(jsonPath("$.links", hasSize(1))) .andExpect(jsonPath("$.links[0].href", is("http://localhost:8080/u/ugnich"))); mockMvc.perform(get("/.well-known/webfinger?resource=acct:durov@localhost")) .andExpect(status().isNotFound()); mockMvc.perform(get("/.well-known/webfinger?resource=acct:@localhost")) .andExpect(status().isNotFound()); } @Test public void userProfileAndBlogShouldBeExposedAsActivityStream() throws Exception { ClassPathResource defaultAvatar = new ClassPathResource("static/av-96.png"); String hash = DigestUtils.md5DigestAsHex(IOUtils.toByteArray(defaultAvatar.getInputStream())); mockMvc.perform(get("/u/ugnich").accept(Context.LD_JSON_MEDIA_TYPE)).andExpect(status().isOk()) .andExpect(jsonPath("$.icon.url", is(String.format("http://localhost:8080/av-96-%s.png", hash)))) .andExpect(jsonPath("$.publicKey.publicKeyPem", is(keystoreManager.getPublicKeyPem()))); jdbcTemplate.execute("DELETE FROM messages"); List mids = IteratorUtils.toList(IntStream.rangeClosed(1, 30) .mapToObj( i -> messagesService.createMessage(ugnich.getUid(), String.format("message %d", i), null, Set.of())) .collect(Collectors.toCollection(ArrayDeque::new)).descendingIterator()); List midsPage = mids.stream().limit(20).toList(); mockMvc.perform(get("/u/ugnich/blog").accept(Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE)) .andExpect(status().isOk()).andExpect(jsonPath("$.orderedItems", hasSize(20))) .andExpect(jsonPath("$.next", is("http://localhost:8080/u/ugnich/blog?before=" + midsPage.get(midsPage.size() - 1)))); } @Test public void postWithoutTagsShouldNotHaveAsteriskInTitle() throws Exception { String msgText = "Привет, я - Угнич"; int mid = messagesService.createMessage(ugnich.getUid(), msgText, null, Set.of()); HtmlPage threadPage = webClient.getPage(String.format("http://localhost:8080/ugnich/%d", mid)); assertThat(threadPage.getTitleText(), equalTo("ugnich:")); } @Test public void repliesList() throws IOException { int mid = messagesService.createMessage(ugnich.getUid(), "hello", null, Set.of()); IntStream.range(1, 15) .forEach(i -> messagesService.createReply(mid, i - 1, freefd, String.valueOf(i - 1), null)); HtmlPage threadPage = webClient.getPage(String.format("http://localhost:8080/ugnich/%d", mid)); assertThat(threadPage.getWebResponse().getStatusCode(), equalTo(200)); Long visibleItems = StreamSupport .stream(threadPage.getHtmlElementById("replies").getChildElements().spliterator(), false) .filter(e -> { StyleElement display = e.getStyleElement("display"); return display == null || !display.getValue().equals("none"); }).count(); assertThat(visibleItems, equalTo(14L)); jdbcTemplate.execute("DELETE FROM messages"); jdbcTemplate.execute("DELETE FROM replies"); } @Test public void userShouldNotSeeReplyButtonToBannedUser() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "freefd bl me", null, Set.of()); messagesService.createReply(mid, 0, ugnich, "yo", null); MvcResult loginResult = mockMvc .perform(post("/login").with(csrf()).param("username", freefdName).param("password", freefdPassword)) .andExpect(status().isFound()).andReturn(); Cookie loginCookie = loginResult.getResponse().getCookie("juick-remember-me"); webClient.setCookieManager(new CookieManager()); webClient.getCookieManager() .addCookie(new com.gargoylesoftware.htmlunit.util.Cookie(loginCookie.getDomain(), loginCookie.getName(), loginCookie.getValue())); HtmlPage threadPage = webClient.getPage(String.format("http://localhost:8080/ugnich/%d", mid)); assertThat(threadPage.getWebResponse().getStatusCode(), equalTo(200)); assertThat(threadPage.querySelectorAll(".msg-comment-target").isEmpty(), equalTo(false)); assertThat(threadPage.querySelectorAll(".a-thread-comment").isEmpty(), equalTo(false)); privacyQueriesService.blacklistUser(freefd, ugnich); assertThat(userService.isInBLAny(freefd.getUid(), ugnich.getUid()), equalTo(true)); User renha = userService.createUser("renha", "secret").orElseThrow(IllegalStateException::new); messagesService.createReply(mid, 0, renha, "people", null); threadPage = webClient.getPage(String.format("http://localhost:8080/ugnich/%d", mid)); assertThat(threadPage.getWebResponse().getStatusCode(), equalTo(200)); assertThat(threadPage.querySelectorAll(".msg-comment-target").isEmpty(), equalTo(true)); assertThat(threadPage.querySelectorAll(".a-thread-comment").isEmpty(), equalTo(true)); } @Test public void correctTagsEscaping() throws PebbleException, IOException { PebbleTemplate template = pebbleEngine.getTemplate("views/test"); Writer writer = new StringWriter(); template.evaluate(writer, Collections.singletonMap("tagsList", Collections.singletonList(new Tag(">_<").getName()))); String output = writer.toString().trim(); assertThat(output, equalTo(">_<")); } public DomElement fetchMeta(String url, String name) throws IOException { HtmlPage page = webClient.getPage(url); DomElement emptyMeta = new DomElement("", "meta", null, null); return page.getElementsByTagName("meta").stream().filter(t -> t.getAttribute("name").equals(name)) .findFirst() .orElse(emptyMeta); } @Test public void testTwitterCards() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "without image", null, Set.of()); assertThat(fetchMeta(String.format("http://localhost:8080/ugnich/%d", mid), "twitter:card") .getAttribute("content"), equalTo("summary")); int mid2 = messagesService.createMessage(ugnich.getUid(), "with image", "png", Set.of()); Message message = messagesService.getMessage(mid2).get(); assertThat(fetchMeta(String.format("http://localhost:8080/ugnich/%d", mid2), "twitter:card") .getAttribute("content"), equalTo("summary_large_image")); assertThat( fetchMeta(String.format("http://localhost:8080/ugnich/%d", mid2), "og:description") .getAttribute("content"), startsWith(StringEscapeUtils.escapeHtml4(MessageUtils.getMessageHashTags(message)))); } @Test public void hashLoginShouldNotUseSession() throws Exception { String hash = userService.getHashByUID(ugnich.getUid()); MvcResult hashLoginResult = mockMvc.perform(get("/?show=my&hash=" + hash)).andExpect(status().isOk()) .andExpect(model().attribute("visitor", hasProperty("authHash", equalTo(hash)))) .andExpect(content().string(containsString(hash))).andReturn(); Cookie rememberMeFromHash = hashLoginResult.getResponse().getCookie("juick-remember-me"); MvcResult formLoginResult = mockMvc .perform(post("/login").with(csrf()).param("username", ugnichName).param("password", ugnichPassword)) .andExpect(status().is3xxRedirection()).andReturn(); Cookie rememberMeFromForm = formLoginResult.getResponse().getCookie("juick-remember-me"); mockMvc.perform(get("/?show=my").cookie(rememberMeFromForm)).andExpect(status().isOk()) .andExpect(model().attribute("visitor", hasProperty("authHash", equalTo(hash)))) .andExpect(content().string(containsString(hash))); mockMvc.perform(get("/?show=my").cookie(rememberMeFromHash)).andExpect(status().isOk()) .andExpect(model().attribute("visitor", hasProperty("authHash", equalTo(hash)))) .andExpect(content().string(containsString(hash))); } @Test public void nonExistentBlogShouldReturn404() throws Exception { mockMvc.perform(get("/ololoe/")).andExpect(status().isNotFound()); } @Test public void discussionsShouldBePageableByTimestamp() throws Exception { String msgText = "Привет, я снова Угнич"; int mid = messagesService.createMessage(ugnich.getUid(), msgText, null, Set.of()); int midNew = messagesService.createMessage(ugnich.getUid(), "Я более новый Угнич", null, Set.of()); MvcResult loginResult = mockMvc .perform(post("/login").with(csrf()).param("username", freefdName).param("password", freefdPassword)) .andExpect(status().is3xxRedirection()).andReturn(); Cookie loginCookie = loginResult.getResponse().getCookie("juick-remember-me"); webClient.setCookieManager(new CookieManager()); webClient.getCookieManager() .addCookie(new com.gargoylesoftware.htmlunit.util.Cookie(loginCookie.getDomain(), loginCookie.getName(), loginCookie.getValue())); String discussionsUrl = "http://localhost:8080/"; HtmlPage discussions = webClient.getPage(discussionsUrl); assertThat(discussions.querySelectorAll("article").size(), is(0)); subscriptionService.subscribeMessage(messagesService.getMessage(mid).get(), freefd); discussions = (HtmlPage) discussions.refresh(); assertThat(discussions.querySelectorAll("article").size(), is(1)); subscriptionService.subscribeMessage(messagesService.getMessage(midNew).get(), freefd); discussions = (HtmlPage) discussions.refresh(); assertThat(discussions.querySelectorAll("article").size(), is(2)); assertThat( discussions.querySelectorAll("article").get(0).getAttributes().getNamedItem("data-mid") .getNodeValue(), is(String.valueOf(midNew))); messagesService.createReply(mid, 0, freefd, "I'm replied", null); discussions = (HtmlPage) discussions.refresh(); assertThat(discussions.querySelectorAll("article").size(), is(2)); assertThat( discussions.querySelectorAll("article").get(0).getAttributes().getNamedItem("data-mid") .getNodeValue(), is(String.valueOf(mid))); Message msg = messagesService.getMessage(mid).get(); HtmlPage discussionsOld = webClient.getPage(discussionsUrl + "?to=" + msg.getUpdated().toEpochMilli()); assertThat(discussionsOld.querySelectorAll("article").size(), is(1)); assertThat(discussionsOld.querySelectorAll("article").get(0).getAttributes().getNamedItem("data-mid") .getNodeValue(), is(String.valueOf(midNew))); List newMids = IntStream.rangeClosed(1, 19) .map(i -> messagesService.createMessage(ugnich.getUid(), String.valueOf(i), null, Set.of())) .boxed() .toList(); for (Integer m : newMids) { subscriptionService.subscribeMessage(messagesService.getMessage(m).get(), freefd); } discussions = (HtmlPage) discussions.refresh(); assertThat(discussions.querySelectorAll("article").size(), is(20)); assertThat( discussions.querySelectorAll("article").get(19).getAttributes().getNamedItem("data-mid") .getNodeValue(), is(String.valueOf(mid))); messagesService.createReply(midNew, 0, freefd, "I'm replied", null); discussions = (HtmlPage) discussions.refresh(); assertThat( discussions.querySelectorAll("article").get(0).getAttributes().getNamedItem("data-mid") .getNodeValue(), is(String.valueOf(midNew))); Message old = messagesService.getMessage(newMids.get(0)).get(); discussionsOld = webClient.getPage(discussionsUrl + "?to=" + old.getUpdated().toEpochMilli()); assertThat(discussionsOld.querySelectorAll("article").size(), is(1)); assertThat(discussionsOld.querySelectorAll("article").get(0).getAttributes().getNamedItem("data-mid") .getNodeValue(), is(String.valueOf(mid))); } @Test public void redirectParamShouldCorrectlyRedirectLoggedUser() throws Exception { MvcResult formLoginResult = mockMvc .perform(post("/login").with(csrf()).param("username", ugnichName).param("password", ugnichPassword)) .andExpect(status().isFound()).andReturn(); Cookie rememberMeFromForm = formLoginResult.getResponse().getCookie("juick-remember-me"); mockMvc.perform(get("/login").cookie(rememberMeFromForm)).andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")); mockMvc.perform(get("/login?retpath=http://localhost:8080/logged_in").cookie(rememberMeFromForm)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://localhost:8080/logged_in")); } @Test public void anythingRedirects() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "yo", null, Set.of()); mockMvc.perform(get(String.format("/%d", mid))).andExpect(status().isMovedPermanently()) .andExpect(redirectedUrl(String.format("/%s/%d", ugnich.getName(), mid))); } @Test public void appAssociationsTest() throws Exception { mockMvc.perform((get("/.well-known/apple-app-site-association"))).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.webcredentials.apps[0]", is(appId))); } @Test public void notificationsTests() throws Exception { jdbcTemplate.execute("DELETE FROM messages"); jdbcTemplate.execute("DELETE FROM replies"); jdbcTemplate.execute("DELETE FROM subscr_messages"); jdbcTemplate.execute("DELETE FROM bl_users"); MvcResult loginResult = mockMvc .perform(post("/login").with(csrf()).param("username", freefdName).param("password", freefdPassword)) .andExpect(status().is3xxRedirection()).andReturn(); Cookie loginCookie = loginResult.getResponse().getCookie("juick-remember-me"); webClient.setCookieManager(new CookieManager()); webClient.getCookieManager() .addCookie(new com.gargoylesoftware.htmlunit.util.Cookie(loginCookie.getDomain(), loginCookie.getName(), loginCookie.getValue())); int mid = messagesService.createMessage(ugnich.getUid(), "new test", null, Set.of()); subscriptionService.subscribeMessage(messagesService.getMessage(mid).get(), freefd); messagesService.createReply(mid, 0, ugnich, "new reply", null); HtmlPage discussionsPage = webClient.getPage("http://localhost:8080/?show=discuss"); assertThat(discussionsPage.querySelectorAll("#global a .badge").size(), is(1)); HtmlPage unreadThread = webClient.getPage(String.format("http://localhost:8080/ugnich/%d", mid)); assertThat(unreadThread.querySelectorAll("#global a .badge").size(), is(0)); messagesService.createReply(mid, 0, ugnich, "reply to ban", null); discussionsPage.refresh(); assertThat(discussionsPage.querySelectorAll("#global a .badge").size(), is(1)); privacyQueriesService.blacklistUser(freefd, ugnich); assertThat(messagesService.getUnread(freefd).size(), is(0)); /* * TODO: fix discussionsPage.refresh(); var unreads = * discussionsPage.querySelectorAll("#global a .badge"); * assertThat(unreads.size(), is(0)); */ privacyQueriesService.blacklistUser(freefd, ugnich); } @Test public void escapeSqlTests() { String sql = String.format("SELECT * FROM table WHERE data='%s'", WebUtils.encodeSphinx("';-- DROP TABLE table")); assertThat(sql, is("SELECT * FROM table WHERE data='\\';-- DROP TABLE table\'")); } @Test public void swaggerOutput() throws Exception { MvcResult result = mockMvc.perform(get("/v3/api-docs").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn(); String outputDir = System.getProperty("io.springfox.staticdocs.outputDir"); if (StringUtils.isNotEmpty(outputDir)) { Files.createDirectories(Paths.get(outputDir)); BufferedWriter writer = Files.newBufferedWriter(Paths.get(outputDir, "swagger.json"), StandardCharsets.UTF_8); writer.write(result.getResponse().getContentAsString()); writer.flush(); } } @Test public void newMessageShouldNotContainDuplicatedTags() throws Exception { CommandResult result = commandsManager.processCommand(ugnich, "*test1 *test2 *test1 test3", emptyUri); assertThat(result.getNewMessage().isPresent(), is(true)); Message msg = result.getNewMessage().get(); List tags = new ArrayList<>(msg.getTags()); assertThat(tags.size(), is(2)); assertThat(tags.get(0).getName(), is("test1")); assertThat(tags.get(1).getName(), is("test2")); assertThat(msg.getText(), is("test3")); } @Test public void oneClickUnsubscribe() throws Exception { mockMvc.perform(post("/settings/unsubscribe").with(csrf()).param("hash", "123456").param( "List-Unsubscribe", "One-Click")) .andExpect(status().isBadRequest()); mockMvc.perform(post("/settings/unsubscribe").param("hash", userService.getHashByUID(ugnich.getUid())) .param("List-Unsubscribe", "One-Click")).andExpect(status().isOk()); } @Test @Order(3) public void ActivityDeserialization() throws IOException { String followJsonStr = IOUtils.toString(new ClassPathResource("follow.json").getURI(), StandardCharsets.UTF_8); Follow follow = (Follow) jsonMapper.readValue(followJsonStr, Context.class); String personJsonStr = IOUtils.toString(new ClassPathResource("person.json").getURI(), StandardCharsets.UTF_8); Person person = (Person) jsonMapper.readValue(personJsonStr, Context.class); String undoJsonStr = IOUtils.toString(new ClassPathResource("undo.json").getURI(), StandardCharsets.UTF_8); Undo undo = jsonMapper.readValue(undoJsonStr, Undo.class); assertThat(undo.getObject(), instanceOf(Follow.class)); String undoFollower = undo.getObject().getId(); String createJsonStr = IOUtils.toString(new ClassPathResource("create.json").getURI(), StandardCharsets.UTF_8); Create create = jsonMapper.readValue(createJsonStr, Create.class); Note note = (Note) create.getObject(); Context attachmentObj = note.getAttachment().get(0); String attachment = attachmentObj != null ? (String) attachmentObj.getUrl() : StringUtils.EMPTY; String deleteJsonStr = IOUtils.toString(new ClassPathResource("delete.json").getURI(), StandardCharsets.UTF_8); Delete delete = jsonMapper.readValue(deleteJsonStr, Delete.class); int mid = messagesService.createMessage(ugnich.getUid(), "YO", "", Set.of()); User extUser = new User(); extUser.setUri(URI.create("http://localhost:8080/users/xwatt")); int rid = messagesService.createReply(mid, 0, extUser, "PEOPLE", null); Message replyFromExt = messagesService.getReply(mid, rid); String extMessageUri = "http://localhost:8080/statuses/12345"; messagesService.updateReplyUri(replyFromExt, URI.create(extMessageUri)); int rid2 = messagesService.createReply(mid, rid, ugnich, "HI", null); Message replyToExt = messagesService.getReply(mid, rid2); Note replyNote = activityPubManager.makeNote(replyToExt); assertThat(replyNote.getInReplyTo(), equalTo(extMessageUri)); String noteStr = IOUtils.toString(new ClassPathResource("mention.json").getURI(), StandardCharsets.UTF_8); Note create2 = jsonMapper.readValue(noteStr, Note.class); jsonMapper.readValue( IOUtils.toString(new ClassPathResource("webfinger.json").getURI(), StandardCharsets.UTF_8), Account.class); NodeInfo info = jsonMapper.readValue( IOUtils.toString(new ClassPathResource("xnodeinfo2.json").getURI(), StandardCharsets.UTF_8), NodeInfo.class); assertThat(info.getUsage().getUsers().getActiveHalfyear(), is(42)); Like like = jsonMapper.readValue( IOUtils.toString(new ClassPathResource("like.json").getURI(), StandardCharsets.UTF_8), Like.class); String undoPleromaStr = IOUtils.toString(new ClassPathResource("undo_pleroma.json").getURI(), StandardCharsets.UTF_8); Undo undoPleroma = jsonMapper.readValue(undoPleromaStr, Undo.class); String undoPleromaFollower = undoPleroma.getObject().getId(); String deletev3JsonStr = IOUtils.toString(new ClassPathResource("delete_v3.json").getURI(), StandardCharsets.UTF_8); Delete deleteObject = jsonMapper.readValue(deletev3JsonStr, Delete.class); } @Test public void activitySerialization() throws Exception { Message msgNoTags = commandsManager.processCommand(ugnich, "people", emptyUri).getNewMessage().get(); String json = jsonMapper.writeValueAsString(Context.build(activityPubManager.makeNote(msgNoTags))); Message msg = commandsManager.processCommand(ugnich, "*NSFW *shit happens", emptyUri).getNewMessage() .get(); Note note = activityPubManager.makeNote(msg); assertThat(note.isSensitive(), is(true)); json = jsonMapper.writeValueAsString(Context.build(note)); Note replyNote = new Note(); replyNote.setId("http://localhost:8080/n/2-1"); replyNote.setInReplyTo(profileUriBuilder.messageUri(msg)); replyNote.setAttributedTo("http://localhost:8080/u/freefd"); replyNote.setTo(Collections.singletonList(profileUriBuilder.personUri(ugnich))); replyNote.setContent("HI"); Create create = new Create(); create.setId(replyNote.getId()); create.setActor("http://localhost:8080/u/freefd"); create.setObject(replyNote); signatureManager.post( (Actor) signatureManager.getContext(URI.create("http://localhost:8080/u/freefd")).get(), (Actor) signatureManager.getContext(URI.create("http://localhost:8080/u/ugnich")).get(), create); Message replyToExt = commandsManager .processCommand(ugnich, String.format("#%d/1 PSSH YOBA ETO TI", msg.getMid()), emptyUri) .getNewMessage() .get(); json = jsonMapper.writeValueAsString(Context.build( activityPubManager.makeNote( messagesService.getReply(replyToExt.getMid(), replyToExt.getRid())))); mockMvc.perform(get("/n/2-0")).andExpect(status().isOk()); mockMvc.perform(get("/n/2222-0")).andExpect(status().isNotFound()); mockMvc.perform(get("/n/2-14")).andExpect(status().isNotFound()); } @Test public void signingSpec() throws IOException, NoSuchAlgorithmException { Actor from = (Actor) signatureManager.getContext(URI.create("http://localhost:8080/u/freefd")).get(); Actor to = (Actor) signatureManager.getContext(URI.create("http://localhost:8080/u/ugnich")).get(); Follow follow = new Follow(); follow.setActor("http://localhost:8080/u/freefd"); follow.setObject(new Context("http://localhost:8080/u/ugnich")); signatureManager.post(from, to, follow); } @Test @Order(1) public void serviceSignatureAuth() throws Exception { String meUri = "/api/me"; Instant now = Instant.now(); String requestDate = DateFormattersHolder.getHttpDateFormatter().format(now); mockMvc.perform(get("/api/me").header("Date", requestDate)).andExpect(status().isUnauthorized()); String testHost = "localhost"; Actor ugnichPerson = profileController.getUser("ugnich"); now = Instant.now(); requestDate = DateFormattersHolder.getHttpDateFormatter().format(now); String signatureString = signatureManager.addSignature(ugnichPerson, testHost, "GET", meUri, requestDate, StringUtils.EMPTY); MvcResult me = mockMvc.perform(get("/api/me").header("Host", testHost).header("Date", requestDate) .header("Signature", signatureString)).andExpect(status().isOk()).andReturn(); User meUser = jsonMapper.readValue(me.getResponse().getContentAsString(), User.class); assertThat(meUser, is(ugnich)); String testuserResponseString = IOUtils.toString(testuserResponse.getInputStream(), StandardCharsets.UTF_8); ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); URI testuserUri = URI.create("https://example.com/u/testuser"); URI testuserkeyUri = URI.create("https://example.com/u/testuser#main-key"); URI testAppUri = URI.create("https://example.com/actor"); URI testAppkeyUri = URI.create("https://example.com/actor#main-key"); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); restServiceServer.expect(times(4), requestTo(testuserUri)) .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON)); restServiceServer.expect(times(4), requestTo(testuserkeyUri)) .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON)); Person testuser = (Person) signatureManager.getContext(testuserUri).get(); assertThat(testuser.getPublicKey().getPublicKeyPem(), is(testKeystoreManager.getPublicKeyPem())); Instant now2 = Instant.now(); String testRequestDate = DateFormattersHolder.getHttpDateFormatter().format(now2); String inboxUri = "/api/inbox"; var payload = IOUtils.toByteArray(testfollowRequest.getInputStream()); byte[] digest = MessageDigest.getInstance("SHA-256").digest(payload); // (1) String digestHeader = "SHA-256=" + new String(Base64.encodeBase64(digest)); String testSignatureString = signatureManager.addSignature(testuser, testHost, "POST", inboxUri, testRequestDate, digestHeader, testKeystoreManager); mockMvc.perform(post(inboxUri).header("Host", testHost).header("Date", testRequestDate) .header("Digest", digestHeader).header("Signature", testSignatureString) .contentType(Context.LD_JSON_MEDIA_TYPE).content(payload)) .andExpect(status().isAccepted()); mockMvc.perform(post(inboxUri).header("Host", "wronghost").header("Date", testRequestDate) .header("Signature", testSignatureString).contentType(Context.LD_JSON_MEDIA_TYPE) .content(IOUtils.toByteArray(testfollowRequest.getInputStream()))) .andExpect(status().isUnauthorized()); // digest required but not present mockMvc.perform(post(inboxUri).header("Host", testHost).header("Date", testRequestDate) .header("Signature", testSignatureString).contentType(Context.LD_JSON_MEDIA_TYPE) .content(payload)) .andExpect(status().isUnauthorized()); apClient.setRequestFactory(originalRequestFactory); } @Test public void testFlaggingAsApplication() throws Exception { var payload = IOUtils.toByteArray(flagPayload.getInputStream()); var digest = MessageDigest.getInstance("SHA-256").digest(payload); // (1) var digestHeader = "SHA-256=" + new String(Base64.encodeBase64(digest)); var now2 = Instant.now(); String inboxUri = "/api/inbox"; String testHost = "localhost"; URI testAppUri = URI.create("https://example.com/actor"); String testappResponseString = IOUtils.toString(testappResponse.getInputStream(), StandardCharsets.UTF_8); var testRequestDate = DateFormattersHolder.getHttpDateFormatter().format(now2); ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); restServiceServer.expect(times(2), requestTo(testAppUri)) .andRespond(withSuccess(testappResponseString, MediaType.APPLICATION_JSON)); Application testapp = (Application) signatureManager.getContext(testAppUri).get(); assertThat(testapp.getPublicKey().getPublicKeyPem(), is(testKeystoreManager.getPublicKeyPem())); var testSignatureString = signatureManager.addSignature(testapp, "localhost", "POST", inboxUri, testRequestDate, digestHeader, testKeystoreManager); mockMvc.perform(post(inboxUri).header("Host", testHost).header("Date", testRequestDate) .header("Signature", testSignatureString).header("Digest", digestHeader) .contentType(Context.LD_JSON_MEDIA_TYPE).content(payload)) .andExpect(status().isAccepted()); apClient.setRequestFactory(originalRequestFactory); } @Test public void serviceUserProfileIsApplicationProfile() throws Exception { MvcResult response = mockMvc.perform(get("/u/juick").accept(Context.ACTIVITY_MEDIA_TYPE)).andReturn(); Actor actor = jsonMapper.readValue(response.getResponse().getContentAsString(), Actor.class); assertThat(actor, is(instanceOf(Application.class))); } @Test public void hostmeta() throws Exception { MvcResult result = mockMvc.perform(get("/.well-known/host-meta")).andExpect(status().isOk()) .andReturn(); String xrd = result.getResponse().getContentAsString(); result = mockMvc.perform(get("/.well-known/x-nodeinfo2")).andExpect(status().isOk()).andReturn(); } @Test public void pms() throws Exception { jdbcTemplate.execute("DELETE FROM pm"); jdbcTemplate.execute("DELETE FROM bl_users"); CommandResult res = commandsManager.processCommand(ugnich, "@freefd DICK", emptyUri); assertThat(res.getNewMessage(), is(Optional.empty())); assertThat(res.getText(), is("Private message sent")); MvcResult result = mockMvc.perform(get("/api/groups_pms").with(httpBasic(freefdName, freefdPassword))) .andExpect(status().isOk()).andReturn(); PrivateChats chats = jsonMapper.readValue(result.getResponse().getContentAsString(), PrivateChats.class); assertThat(chats.getUsers().size(), is(1)); } @Test public void seenTests() { Instant now = Instant.now(); User newUser = userService.createUser("newuser", "assword").orElseThrow(IllegalStateException::new); assertThat(newUser.getUid(), greaterThanOrEqualTo(0)); assertThat(newUser.getSeen(), is(nullValue())); messagesService.createMessage(newUser.getUid(), "YO", "", Set.of()); assertThat(userService.getUserByUID(newUser.getUid()).get().getSeen(), greaterThanOrEqualTo(now)); } @Test public void signupTest() throws Exception { emailService.addVerificationCode(null, "demo@email.com", "123456"); MvcResult result = mockMvc.perform(post("/api/signup").param("username", "testuser") .param("password", "demopassword").param("verificationCode", "123456")) .andExpect(status().isOk()) .andReturn(); User testuser = jsonMapper.readValue(result.getResponse().getContentAsString(), User.class); assertThat(testuser.getName(), is("testuser")); } @Test public void doNotAllowRepliesToNonExistingReplyAndNotAllowToEditTagsOfComment() throws Exception { assertThat(commandsManager.processCommand(ugnich, "#23213213/2 BAD COMMENT", emptyUri).getText(), is("Message not found")); Message msg = commandsManager.processCommand(ugnich, "YO", emptyUri).getNewMessage().get(); assertThat(commandsManager .processCommand(ugnich, String.format("#%d/1 BAD COMMENT", msg.getMid()), emptyUri) .getText(), is("Reply not found")); CommandResult result = commandsManager.processCommand(freefd, String.format("#%d *GOOD *COMMENT", msg.getMid()), emptyUri); Message reply = result.getNewMessage().get(); assertThat( commandsManager.processCommand(ugnich, String.format("#%d/%d *GOOD *BAD", reply.getMid(), reply.getRid()), emptyUri).getText(), startsWith("Reply posted")); } @Test public void XMPPSignupIsDisabled() throws Exception { jdbcTemplate.execute("DELETE FROM jids"); jdbcTemplate.update("INSERT INTO jids(loginhash, jid) VALUES('1', 'test@jid.tld')"); MvcResult formLoginResult = mockMvc .perform(post("/login").with(csrf()).param("username", ugnichName).param("password", ugnichPassword)) .andExpect(status().is3xxRedirection()).andReturn(); Cookie rememberMeFromForm = formLoginResult.getResponse().getCookie("juick-remember-me"); mockMvc.perform(post("/signup").with(csrf()).cookie(rememberMeFromForm).param("hash", "1") .param("type", "xmpp") .param("action", "link")).andExpect(status().isOk()) .andExpect(content().string(containsString("XMPP support is disabled"))); } @Test public void ActivityPubLikesShouldNotMirrorMessage() throws Exception { jdbcTemplate.execute("DELETE FROM messages"); Message msg = commandsManager.processCommand(ugnich, "YO", emptyUri).getNewMessage().get(); messagesService.likeMessage(msg.getMid(), freefd.getUid(), Reaction.LIKE); messagesService.likeMessage(msg.getMid(), 0, Reaction.LIKE, "http://localhost:8080/u/test"); messagesService.recommendMessage(msg.getMid(), serviceUser.getUid()); List top = messagesService.getMessages(ugnich, messagesService.getUserBlogWithRecommendations(serviceUser, ugnich, 0, 0)); assertThat(top.size(), is(1)); } @Test public void verifiedUsersTest() { assertThat(userService.getUserByName("ugnich").isVerified(), is(false)); jdbcTemplate.update("INSERT INTO telegram(user_id, tg_id, tg_name) VALUES(?, ?, ?)", ugnich.getUid(), 100001866137681L, "tg_test"); assertThat(userService.canDeleteTelegramUser(userService.getUserByName("ugnich")), is(false)); userService.addFacebookState("12345", "http://localhost"); userService.createFacebookUser(12345, "12345", "5678", "ugnich"); userService.setFacebookUser("12345", ugnich.getUid()); assertThat(userService.getUserByName("ugnich").isVerified(), is(true)); assertThat(userService.canDeleteTelegramUser(userService.getUserByName("ugnich")), is(true)); jdbcTemplate.update("DELETE FROM facebook"); assertThat(userService.canDeleteTelegramUser(userService.getUserByName("ugnich")), is(false)); } @Test @Disabled("FIXME: profile changed as expected, but cache is not refreshed or something") public void changeProfileOverApi() throws Exception { String imgDir = storageService.getImageDirectory(); ClassPathResource defaultAvatar = new ClassPathResource("static/av-96.png"); String hash = DigestUtils.md5DigestAsHex(IOUtils.toByteArray(defaultAvatar.getInputStream())); assertThat(webApp.getAvatarUrl(userService.getUserByName(freefdName)), is(String.format("http://localhost:8080/av-96-%s.png", hash))); ClassPathResource newAvatar = new ClassPathResource("static/durov.png"); byte[] newAvatarData = IOUtils.toByteArray(newAvatar.getInputStream()); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/me/upload") .file(new MockMultipartFile("avatar", "durov.png", "image/png", newAvatarData)) .with(httpBasic(freefdName, freefdPassword))).andExpect(status().isOk()); String newHash = DigestUtils.md5DigestAsHex(newAvatarData); URI newUri = Paths.get(imgDir, "ao", String.format("%d.png", freefd.getUid())).toUri(); assertThat(DigestUtils.md5DigestAsHex(IOUtils.toByteArray(newUri)), is(newHash)); URI convertedAvatarUri = Paths.get(imgDir, "a", String.format("%d.png", freefd.getUid())).toUri(); String convertedAvatarHash = DigestUtils.md5DigestAsHex(IOUtils.toByteArray(convertedAvatarUri)); mockMvc.perform(get("/api/me").with(httpBasic(freefdName, freefdPassword))).andExpect(status().isOk()) .andExpect(jsonPath("$.avatar", is( String.format("http://localhost:8080/i/a/%d-%s.png", freefd.getUid(), convertedAvatarHash)))); mockMvc.perform(post("/api/me").with(httpBasic(ugnichName, ugnichPassword)).param("password", "newPassword")) .andExpect(status().isOk()); mockMvc.perform(get("/api/me").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isUnauthorized()); mockMvc.perform(post("/api/me").with(httpBasic(ugnichName, "newPassword")).param("password", ugnichPassword)) .andExpect(status().isOk()); mockMvc.perform(get("/api/me").with(httpBasic(ugnichName, ugnichPassword))).andExpect(status().isOk()); assertThat(usersController.getMe(ugnich).getJIDs().size(), is(0)); jdbcTemplate.update("INSERT INTO jids(user_id, jid) VALUES(?, ?)", ugnich.getUid(), "test@example.com"); jdbcTemplate.update("INSERT INTO jids(user_id, jid) VALUES(?, ?)", ugnich.getUid(), "test2@example.com"); assertThat(usersController.getMe(ugnich).getJIDs().size(), is(2)); mockMvc.perform( post("/api/me").with(httpBasic(ugnichName, ugnichPassword)).param("jid-del", "test@example.com")) .andExpect(status().isOk()); assertThat(usersController.getMe(ugnich).getJIDs().size(), is(1)); mockMvc.perform( post("/api/me").with(httpBasic(ugnichName, ugnichPassword)).param("jid-del", "test2@example.com")) .andExpect(status().isBadRequest()); jdbcTemplate.execute("DELETE FROM jids"); } @Test public void varyMvcResponse() throws Exception { mockMvc.perform(get("/")).andExpect(status().isOk()) .andExpect(header().string("Vary", "Accept-Language")); mockMvc.perform(get("/rss/ugnich/blog")).andExpect(status().isOk()) .andExpect(header().string("Vary", "Accept-Language")); mockMvc.perform(get("/api/messages")).andExpect(status().isOk()) .andExpect(header().string("Vary", "Accept-Language")); } @Test public void apiInfo() throws Exception { userService.createUser("tst", "tst"); MvcResult result = mockMvc.perform(get("/api/info/tst")).andExpect(status().isOk()).andReturn(); User tst = jsonMapper.readValue(result.getResponse().getContentAsString(), User.class); assertThat(tst.getReaders(), is(nullValue())); commandsManager.processCommand(ugnich, "S @tst", emptyUri); result = mockMvc.perform(get("/api/info/tst")).andExpect(status().isOk()).andReturn(); tst = jsonMapper.readValue(result.getResponse().getContentAsString(), User.class); assertThat(tst.getReaders().size(), is(1)); } @Test public void federatedUserDeletionFlowWhenItIsGone() throws Exception { String deleteJsonStr = IOUtils.toString(new ClassPathResource("delete_user.json").getURI(), StandardCharsets.UTF_8); Delete delete = jsonMapper.readValue(deleteJsonStr, Delete.class); ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); restServiceServer.expect(times(2), requestTo(delete.getObject().getId())) .andRespond(withStatus(HttpStatus.GONE)); restServiceServer.expect(requestTo(delete.getObject().getId())).andRespond(response -> { throw new ResourceAccessException("Connection reset"); }); mockMvc.perform(post("/api/inbox").contentType(ACTIVITY_MEDIA_TYPE).content(deleteJsonStr)) .andExpect(status().isAccepted()); mockMvc.perform(post("/api/inbox").contentType(ACTIVITY_MEDIA_TYPE).content(deleteJsonStr).header( "Signature", "keyId=\"https://example.com/users/deleted#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"wHoU91JJBsIYcR1W1/57B0oG98t5Aa/TvGPw1B8KQlAp5KhpePnOzD1MZRgivBx7YKO6eYwDx+AX9dn6tjlAvzRLygv21H6UoDZFihWzeE1HM8pY2Pe4EhUgYBN0YuiKUi7W4TS9bDRAJ5vGNPUWATe+2o5Jcbux5cZYXFKKYbLBLD+/IlqPdHA2IXLZ52HFVVfBkPH5sSklV6XJtD/PHLK9R/I9w/mUpj9moUPQu44rR7KvxiGNuHla3vfDtJbkBqLMdScX91EG8373AulXPUiCCF7R2lJB0fFQedm2nSbcwBoJ32GEyOyOPFgPKG5zd9Fd5TfB1pmA8ZIE0sChfA==\"")) .andExpect(status().isAccepted()); apClient.setRequestFactory(originalRequestFactory); } @MockBean private MockDeleteListener deleteListener; @Captor protected ArgumentCaptor deleteEventCaptor; @Test public void federatedUserDeletionFlowWhenItIsSuspended() throws Exception { String deleteJsonStr = IOUtils.toString(testDeleteRequest.getInputStream(), StandardCharsets.UTF_8); Delete delete = jsonMapper.readValue(deleteJsonStr, Delete.class); ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); restServiceServer.expect(times(2), requestTo(delete.getObject().getId())) .andRespond(withSuccess( IOUtils.toString(testSuspendedUserResponse.getInputStream(), StandardCharsets.UTF_8), MediaType.APPLICATION_JSON)); Person testuser = (Person) signatureManager.getContext(URI.create(delete.getObject().getId())).get(); Instant now = Instant.now(); String testRequestDate = DateFormattersHolder.getHttpDateFormatter().format(now); String inboxUri = "/api/inbox"; byte[] digest = MessageDigest.getInstance("SHA-256").digest(deleteJsonStr.getBytes()); String digestHeader = "SHA-256=" + new String(Base64.encodeBase64(digest)); String testSignatureString = signatureManager.addSignature(testuser, "localhost", "POST", inboxUri, testRequestDate, digestHeader, testKeystoreManager); mockMvc.perform(post(inboxUri).contentType(ACTIVITY_MEDIA_TYPE).content(deleteJsonStr) .header("Host", "localhost").header("Date", testRequestDate) .header("Digest", digestHeader) .header("Signature", testSignatureString)).andExpect(status().isAccepted()); apClient.setRequestFactory(originalRequestFactory); Mockito.verify(deleteListener, Mockito.times(1)).onApplicationEvent(deleteEventCaptor.capture()); DeleteUserEvent receivedEvent = deleteEventCaptor.getValue(); assertThat(receivedEvent.getUserUri(), is(testuser.getId())); } @Test @Order(2) public void handleIncorrectCertificates() throws Exception { String deleteJsonStr = IOUtils.toString(new ClassPathResource("delete_user.json").getURI(), StandardCharsets.UTF_8); Delete delete = jsonMapper.readValue(deleteJsonStr, Delete.class); ClientHttpRequestFactory originalRequestFactory = apClient.getRequestFactory(); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); restServiceServer.expect(requestTo(delete.getObject().getId())).andRespond(response -> { throw new ResourceAccessException("Connection reset"); }); mockMvc.perform(post("/api/inbox").contentType(ACTIVITY_MEDIA_TYPE).content(deleteJsonStr).header( "Signature", "keyId=\"https://example.com/users/deleted#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"wHoU91JJBsIYcR1W1/57B0oG98t5Aa/TvGPw1B8KQlAp5KhpePnOzD1MZRgivBx7YKO6eYwDx+AX9dn6tjlAvzRLygv21H6UoDZFihWzeE1HM8pY2Pe4EhUgYBN0YuiKUi7W4TS9bDRAJ5vGNPUWATe+2o5Jcbux5cZYXFKKYbLBLD+/IlqPdHA2IXLZ52HFVVfBkPH5sSklV6XJtD/PHLK9R/I9w/mUpj9moUPQu44rR7KvxiGNuHla3vfDtJbkBqLMdScX91EG8373AulXPUiCCF7R2lJB0fFQedm2nSbcwBoJ32GEyOyOPFgPKG5zd9Fd5TfB1pmA8ZIE0sChfA==\"")) .andExpect(status().isAccepted()); apClient.setRequestFactory(originalRequestFactory); } @Test public void legacyAvatarEndpoint() throws Exception { mockMvc.perform(get("/api/avatar").param("uname", "unknown")).andExpect(status().isOk()) .andExpect(content().bytes(IOUtils.toByteArray(defaultAvatar.getInputStream()))); } @Test public void federatedAttachmentsAsLinks() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "test", StringUtils.EMPTY, Set.of()); Message testMessage = MockUtils.mockMessage(mid, freefd, "reply"); String activity = IOUtils.toString(noteWithDocument.getInputStream(), StandardCharsets.UTF_8); Announce announce = jsonMapper.readValue(activity, Announce.class); String noteString = IOUtils.toString(noteWithAttachment.getInputStream(), StandardCharsets.UTF_8); Create create = jsonMapper.readValue(noteString, Create.class); Note note = (Note) create.getObject(); String markdown = remarkConverter.convertFragment((String) note.getContent()); String commandBody = note.getContent() == null ? markdown : note.getAttachment().stream().map(attachment -> { String attachmentUrl = attachment.getUrl(); String attachmentName = attachment.getName(); return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); }).reduce(markdown, (current, next) -> String.format("%s\n%s", current, next)); } @Test public void hubzillaAndHonkActorAndFriendicaUpdate() throws Exception { String activity = IOUtils.toString(hubzillaActivity.getInputStream(), StandardCharsets.UTF_8); Create create = jsonMapper.readValue(activity, Create.class); String followData = IOUtils.toString(hubzillaFollow.getInputStream(), StandardCharsets.UTF_8); Follow follow = jsonMapper.readValue(followData, Follow.class); assertThat(follow.getActor(), is("https://ussr.win/channel/zlax")); String honkData = IOUtils.toString(honkFollow.getInputStream(), StandardCharsets.UTF_8); Follow hfollow = jsonMapper.readValue(honkData, Follow.class); assertThat(hfollow.getTo().get(0), is("https://juick.com/u/vt")); String updateString = IOUtils.toString(friendicaUpdate.getInputStream(), StandardCharsets.UTF_8); Update update = jsonMapper.readValue(updateString, Update.class); } @Test public void nodeinfo() throws Exception { MvcResult nodeinfoXRD = mockMvc .perform(get("/.well-known/nodeinfo").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn(); JsonNode node = jsonMapper.readTree(nodeinfoXRD.getResponse().getContentAsString()); assertThat(node.get("links"), notNullValue()); String nodeinfoUrl = node.get("links").get(0).get("href").textValue(); MvcResult nodeinfoData = mockMvc.perform(get(nodeinfoUrl).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn(); JsonNode nodeinfo = jsonMapper.readTree(nodeinfoData.getResponse().getContentAsString()); assertThat(nodeinfo.get("software"), notNullValue()); assertThat(nodeinfo.get("server"), nullValue()); MvcResult xnodeinfoData = mockMvc .perform(get("/.well-known/x-nodeinfo2").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andReturn(); JsonNode xnodeinfo = jsonMapper.readTree(xnodeinfoData.getResponse().getContentAsString()); assertThat(xnodeinfo.get("server"), notNullValue()); assertThat(xnodeinfo.get("software"), nullValue()); } @Test public void anonymousUserFromZero() { User user = userService.getUserByUID(0).orElse(AnonymousUser.INSTANCE); assertThat(user.isAnonymous(), is(true)); } @Test public void messagePropertiesTest() { int mid = messagesService.createMessage(ugnich.getUid(), "YO", null, Set.of()); messagesService.setMessageProperty(mid, 0, "tg_id", "YO"); assertThat(messagesService.getMessageProperty(mid, 0, "tg_id"), is("YO")); messagesService.setMessageProperty(mid, 0, "tg_id", "YO2"); assertThat(messagesService.getMessageProperty(mid, 0, "tg_id"), is("YO2")); Pair messageId = messagesService.findMessageByProperty("tg_id", "YO2").orElseThrow(); assertThat(messageId.getLeft(), is(mid)); messagesService.setMessageProperty(mid, 0, "tg_id", ""); assertThat(messagesService.getMessageProperty(mid, 0, "tg_id"), is(StringUtils.EMPTY)); int rid = messagesService.createReply(mid, 0, ugnich, "EOPLE", null); messagesService.setMessageProperty(mid, rid, "tg_id", "hrhr"); Pair replyId = messagesService.findMessageByProperty("tg_id", "hrhr").orElseThrow(); assertThat(replyId.getRight(), is(rid)); } @Test public void forbiddenForAnonymousEndpoints() throws Exception { mockMvc.perform(post("/api/comment")).andExpect(status().isUnauthorized()); mockMvc.perform(post("/api/like")).andExpect(status().isUnauthorized()); mockMvc.perform(post("/api/subscribe")).andExpect(status().isUnauthorized()); mockMvc.perform(post("/api/react")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/api/notifications")).andExpect(status().isUnauthorized()); mockMvc.perform(delete("/api/notifications")).andExpect(status().isUnauthorized()); } @Test public void rssFeeds() throws Exception { mockMvc.perform(get("/rss/ugnich/blog").accept(MediaType.TEXT_XML)).andExpect(status().isOk()); // fallback mockMvc.perform(get("/rss/ugnich/blog").header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;" + "q=0.8,application/signed-exchange;v=b3")) .andExpect(status().isOk()) .andExpect(content().contentType("application/rss+xml;charset=UTF-8")); mockMvc.perform(get("/rss/ugnich/feed").accept(MediaType.TEXT_XML)).andExpect(status().isOk()); mockMvc.perform(get("/rss/ugnich/diary").accept(MediaType.TEXT_XML)).andExpect(status().isNotFound()); } @Test public void wsThreadsShouldRedirect() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "tst", null, Set.of()); mockMvc.perform(get("/ugnich/" + mid)).andExpect(status().isOk()); mockMvc.perform(get("/s/" + mid)).andExpect(status().isFound()) .andExpect(redirectedUrl("/ugnich/" + mid)); mockMvc.perform(get("/ws/" + mid)).andExpect(status().isFound()) .andExpect(redirectedUrl("/ugnich/" + mid)); } @MockBean private MockNotificationListener notificationListener; @Captor protected ArgumentCaptor topEventCaptor; @Test public void topEventShouldNotLossUser() { Message topMessage = MockUtils.mockMessage(1000, ugnich, "top message"); topMessage.setTo(AnonymousUser.INSTANCE); SystemEvent event = new SystemEvent(this, SystemActivity.like(serviceUser, topMessage, Collections.emptyList())); applicationEventPublisher.publishEvent(event); Mockito.verify(notificationListener, Mockito.times(1)).onApplicationEvent(topEventCaptor.capture()); SystemEvent receivedEvent = topEventCaptor.getValue(); assertThat(receivedEvent.getActivity().getMessage().getUser(), is(ugnich)); } @Test public void tagStatsSpec() throws Exception { String newUserName = "tagger"; String newUserSecret = "secret"; User newUser = userService.createUser(newUserName, newUserSecret) .orElseThrow(IllegalStateException::new); commandsManager.processCommand(newUser, "*test yo", emptyUri); commandsManager.processCommand(newUser, "*test yo2", emptyUri); commandsManager.processCommand(newUser, "*rare yo3", emptyUri); MvcResult userResponse = mockMvc.perform(get("/api/me").with(httpBasic(newUserName, newUserSecret))) .andExpect(status().isOk()).andReturn(); User userData = jsonMapper.readValue(userResponse.getResponse().getContentAsString(), User.class); List userTags = userData.getTagStats(); assertThat(userTags.size(), is(2)); TagStats rareTagStats = userTags.stream().filter(tagStats -> tagStats.getTag().getName().equals("rare")) .findFirst().orElseThrow(IllegalStateException::new); assertThat(rareTagStats.getUsageCount(), is(1)); } private String getSnapshot(Resource resource) throws IOException { return IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8); } @Test public void emailTemplatesTest() throws IOException { String plainText = webApp.renderPlaintext("yo", "https://localhost/m/1").orElseThrow(); assertThat(plainText, is(getSnapshot(testSubscriptionTextEmail))); User demo = MockUtils.mockUser(45, ugnichName, ugnichPassword); Message html = MockUtils.mockMessage(56, demo, "yo"); String htmlText = webApp .renderHtml(MessageUtils.formatHtml(html), PlainTextFormatter.formatUrl(html), html, "12345") .orElseThrow(); assertThat(htmlText, is(getSnapshot(testSubscriptionHtmlEmail))); html.setMid(0); String htmlPM = webApp .renderHtml(MessageUtils.formatHtml(html), PlainTextFormatter.formatUrl(html), html, "12345") .orElseThrow(); assertThat(htmlPM, is(getSnapshot(testPrivateHtmlEmail))); } @Test public void readonlyTest() throws Exception { var result = commandsManager.processCommand(ugnich, "*readonly YO", emptyUri); var mid = result.getNewMessage().get().getMid(); var readonlyResult = commandsManager.processCommand(freefd, String.format("#%d PEOPLE", mid), emptyUri); assertThat(readonlyResult.getNewMessage().isPresent(), is(false)); var authorResult = commandsManager.processCommand(ugnich, String.format("#%d PEOPLE", mid), emptyUri); assertThat(authorResult.getNewMessage().isPresent(), is(true)); commandsManager.processCommand(ugnich, String.format("#%d *readonly", mid), emptyUri); Message updatedMessage = messagesService.getMessage(mid).orElseThrow(); assertThat(updatedMessage.ReadOnly, is(false)); } @Test public void activitySerializationTest() throws JsonProcessingException { var freefd = MockUtils.mockUser(10, "freefd", "secret"); var like = SystemActivity.like(MockUtils.mockUser(1, "ugnich", "secret"), MockUtils.mockMessage(1, freefd, "txt"), Collections.singletonList(freefd)); var likeStr = jsonMapper.writeValueAsString(like); } @Test public void testAppleClientSecret() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException, NoSuchProviderException { String secret = new String(clientSecretGenerator.getClientSecret().getBytes(), StandardCharsets.UTF_8); final java.security.Key publicKey = clientSecretGenerator.getPublicKey(); Jws jwt = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(secret); assertThat(jwt.getHeader().get("kid"), is("keyid")); assertThat(jwt.getHeader().get("alg"), is("ES256")); Claims claims = jwt.getBody(); assertThat(claims.get("iss"), is("teamid")); assertThat(claims.get("sub"), is("com.example.app")); assertThat(claims.get("aud"), is("https://appleid.apple.com")); } @Test public void linksTest() throws IOException, ParserConfigurationException, SAXException { Site site = Site.fromXMLData(IOUtils.toString(sapeOutput.getInputStream(), StandardCharsets.UTF_8)); assertThat(site.pages().size(), is(3)); assertThat(site.pages().get(0).links().size(), is(2)); assertThat(site.code(), is("")); SapePageLinks botLinks = new SapePageLinks(site, "ugnich", URI.create("http://localhost/"), "ugnich"); assertThat(botLinks.render(), is("")); SapePageLinks visitorLinks = new SapePageLinks(site, "ugnich", URI.create("http://localhost/"), null); assertThat(visitorLinks.render(), is( " Тест ссылки - passed. . Тест ссылки 2 - passed. ")); SapePageLinks emptyLinks = new SapePageLinks(site, "ugnich", URI.create("http://localhost/yo"), null); assertThat(emptyLinks.render(), is(emptyString())); } @Test public void invalidMediaTypeTest() throws Exception { mockMvc.perform(get("/api/messages") .header("Accept", "application/xml")).andExpect(status().isBadRequest()) .andExpect(content().string("Invalid media type")); } @Test public void emptyContextShouldNotSerializeType() throws Exception { Context context = new Context("http://juick.com/u/ermine"); String contextString = jsonMapper.writeValueAsString(context); assertThat(contextString, is("{\"id\":\"http://juick.com/u/ermine\"}")); } @Test public void emptyListForEmptyChatsList() throws Exception { jdbcTemplate.execute("DELETE FROM pm"); mockMvc.perform( get("/api/groups_pms").with(httpBasic(ugnichName, ugnichPassword))) .andExpect(status().isOk()) .andExpect(jsonPath("$.pms", empty())); } @Test public void ldRequestToThreadShouldRedirect() throws Exception { var result = commandsManager.processCommand(ugnich, "test", emptyUri); var mid = result.getNewMessage().get().getMid(); var htmlUri = String.format("/ugnich/%d", mid); var ldUri = String.format("/n/%d-0", mid); mockMvc.perform(get(htmlUri).accept(Context.LD_JSON_MEDIA_TYPE)).andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl(ldUri)); mockMvc.perform(get(htmlUri).accept(MediaType.APPLICATION_JSON)).andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl(ldUri)); } @Test @Transactional public void unsignedOverlowTest() throws Exception { var result = commandsManager.processCommand(ugnich, "test", emptyUri); var mid = result.getNewMessage().get().getMid(); jdbcTemplate.update("UPDATE subscr_messages SET last_read_rid=10 WHERE message_id=?", mid); assertThat(messagesService.getMessages(ugnich, List.of(mid)).size(), is(1)); assertThat(messagesService.getMessages(ugnich, List.of(mid)).get(0).isUnread(), is(false)); } @Test public void shareUrlShouldRedirectToPost() throws Exception { mockMvc.perform(get("/share?text=Hello\nWorld")).andExpect(redirectedUrl("/post?body=Hello%0AWorld")); } @Test public void mentionUrlsShouldRedirectToExternalUser() throws Exception { mockMvc.perform(get("/mention?username=vt@juick.com")).andExpect( redirectedUrl("https://juick.com/vt/") ); } @Test public void userInfoUpdateTest() { assertThat(ugnich.getFullName(), equalTo(null)); var fullUgnichName = "Anton Ugnich"; ugnich.setFullName(fullUgnichName); userService.updateUserInfo(ugnich); var info = userService.getUserInfo(ugnich); assertThat(info.getName(), equalTo(ugnichName)); assertThat(info.getFullName(), equalTo(fullUgnichName)); ugnich.setDescription("Test"); userService.updateUserInfo(ugnich); var descr = userService.getUserInfo(ugnich); assertThat(descr.getDescription(), equalTo("Test")); } @Test public void givenAccessSecuredResource_whenAuthenticated_thenRedirectedBack() throws Exception { MockHttpServletRequestBuilder securedResourceAccess = get("/settings"); MvcResult unauthenticatedResult = mockMvc.perform(securedResourceAccess).andExpect(status().is3xxRedirection()) .andReturn(); MockHttpSession session = (MockHttpSession) unauthenticatedResult.getRequest().getSession(); String loginUrl = unauthenticatedResult.getResponse().getRedirectedUrl(); mockMvc.perform(post(loginUrl).param("username", ugnichName).param("password", ugnichPassword) .session(session).with(csrf())).andExpect(status().is3xxRedirection()) .andExpect(redirectedUrlPattern("**/settings?continue")).andReturn(); mockMvc.perform(securedResourceAccess.session(session)).andExpect(status().isOk()); } /* @Test public void tokenAuth() throws Exception { var token = keystoreManager.generateToken(ugnich); mockMvc.perform(get("/api/me") .header("Authorization", "Bearer " + token)) .andExpect(jsonPath("$.uname", is("ugnich"))); token = keystoreManager.generateToken(freefd); mockMvc.perform(get("/api/me") .header("Authorization", "Bearer " + token)) .andExpect(jsonPath("$.uname", is("freefd"))); token = keystoreManager.generateToken(ugnich) + "1"; mockMvc.perform(get("/api/me") .header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()); } */ }