/* * 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.util.*; import com.juick.util.formatters.PlainTextFormatter; import com.juick.model.*; import com.juick.server.*; 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.model.Context; import com.juick.www.api.activity.model.activities.*; 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.juick.www.WebApp; import com.juick.service.*; import com.juick.service.activities.UpdateEvent; import com.juick.service.component.SystemEvent; import com.juick.test.util.MockUtils; import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.template.PebbleTemplate; import com.overzealous.remark.Remark; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; 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.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.interfaces.ECPrivateKey; import org.bouncycastle.jce.interfaces.ECPublicKey; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; 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.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; 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.MockMvcRequestBuilders; 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.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import rocks.xmpp.addr.Jid; import rocks.xmpp.core.session.Extension; import rocks.xmpp.core.session.XmppSession; import rocks.xmpp.core.session.XmppSessionConfiguration; import javax.inject.Inject; import javax.servlet.http.Cookie; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.*; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.sql.Timestamp; import java.time.Instant; 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 junit.framework.TestCase.assertTrue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; 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. */ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @TestPropertySource(properties = { "ios_app_id=12345678.com.juick.ExampleApp", "juick.admin_users=ugnich" }) @AutoConfigureMockMvc 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 PMQueriesService pmQueriesService; @Inject private TelegramService telegramService; @Inject private CrosspostService crosspostService; @Inject private ServerManager serverManager; @Inject private KeystoreManager keystoreManager; @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") private String tmpDir; @Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}") private String imgDir; @Inject private PebbleEngine pebbleEngine; @Value("${ios_app_id:}") private String appId; @Inject private SignatureManager signatureManager; @Inject private ActivityPubManager activityPubManager; @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/testfollow.json") private Resource testfollowRequest; @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: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; @Inject AppleClientSecretGenerator clientSecretGenerator; @Inject private Remark remarkConverter; @Inject private KeystoreManager testKeystoreManager; @Inject private ApplicationEventPublisher applicationEventPublisher; @Inject private Users usersController; private static User ugnich, freefd, juick; static String ugnichName, ugnichPassword, freefdName, freefdPassword, juickName, juickPassword; URI emptyUri = URI.create(StringUtils.EMPTY); private static boolean isSetUp = false; @Before public void setUp() throws Exception { FileSystemUtils.deleteRecursively(Paths.get(imgDir, "p")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "photos-1024")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "photos-512")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "ps")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "a")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "ao")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "as")); 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!"; juickName = "juick"; juickPassword = "demo"; ugnich = userService.createUser(ugnichName, ugnichPassword).orElseThrow(IllegalStateException::new); freefd = userService.createUser(freefdName, freefdPassword).orElseThrow(IllegalStateException::new); juick = userService.createUser(juickName, juickPassword).orElseThrow(IllegalStateException::new); webClient.getOptions().setJavaScriptEnabled(false); isSetUp = true; } MockitoAnnotations.initMocks(this); } @After public void teardown() throws IOException { FileSystemUtils.deleteRecursively(Paths.get(imgDir, "p")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "photos-1024")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "photos-512")); FileSystemUtils.deleteRecursively(Paths.get(imgDir, "ps")); } @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, null); int mid2 = messagesService.createMessage(ugnich.getUid(), "test2", null, null); 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, null); 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).getLikes(), equalTo(1)); assertThat(messagesService.recommendMessage(mid0, ugnich.getUid()), equalTo(MessagesService.RecommendStatus.Deleted)); assertThat(messagesService.getMessage(mid0).get().getLikes(), 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, Collections.singletonList(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); crosspostService.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.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() { pmQueriesService.createPM(freefd.getUid(), ugnich.getUid(), "hello"); Message pm = pmQueriesService.getPMMessages(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("it should be me", "mmmme", user.getName()); int mid = messagesService.createMessage(user.getUid(), "yo", null, new ArrayList<>()); 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); List tagList = new ArrayList<>(); tagList.add(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("db tags should not be escaped", dbTagName, htmlTag.getName()); int mid4 = messagesService.createMessage(user.getUid(), "yoyoyo", null, null); Message msg4 = messagesService.getMessage(mid4).get(); assertEquals("tags string should be empty", StringUtils.EMPTY, MessageUtils.getTagsString(msg4)); messagesService.deleteMessage(user.getUid(), mid4); } public ExpectedException exception = ExpectedException.none(); @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, new ArrayList<>()); 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.getLikes(), equalTo(1)); Assert.assertEquals(2, msg4.getReactions().stream().filter(r -> r.getId() == 2) .findFirst().orElseThrow(IllegalStateException::new).getCount()); Assert.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, null); 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, new ArrayList<>()); 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, Arrays.asList(yo, weather)); messagesService.createMessage(freefd.getUid(), "text2", null, Collections.singletonList(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(juickName, juickPassword))) .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).getToken(), 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(juickName, juickPassword)) .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(juickName, juickPassword)) .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, null); 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)); 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)); } @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()); 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 { 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("text should match", "yoyo", messagesService.getMessage(msg.getMid()).get().getText()); assertEquals("tag should match", "yo", tagService.getMessageTags(msg.getMid()).get(0).getTag().getName()); CommandResult yoyoMsg = commandsManager.processCommand(user, "*yo", URI.create("https://static.juick.com/settings/facebook.png")); assertTrue(yoyoMsg.getNewMessage().isPresent()); assertThat(yoyoMsg.getNewMessage().get().getTags().stream().findFirst().get(), is(yo)); Message msg2 = yoyoMsg.getNewMessage().get(); int mid = msg2.getMid(); Timestamp last = jdbcTemplate.queryForObject("SELECT lastmessage FROM users WHERE id=?", Timestamp.class, user.getUid()); assertThat(last.toInstant(), equalTo(yoyoMsg.getNewMessage().get().getCreated())); assertEquals("should be message", 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("should be subscribed", "Subscribed", commandsManager.processCommand(readerUser, "S #" + mid, emptyUri).getText()); assertEquals("should be favorited", "Message is added to your recommendations", commandsManager.processCommand(readerUser, "! #" + mid, emptyUri).getText()); int rid = messagesService.createReply(mid, 0, user, "comment", null); assertEquals("number of subscribed users should match", 1, subscriptionService.getUsersSubscribedToComments( messagesService.getMessage(mid).get(), messagesService.getReply(mid, rid)).size()); privacyQueriesService.blacklistUser(user, readerUser); assertEquals("number of subscribed users should match", 0, subscriptionService.getUsersSubscribedToComments( messagesService.getMessage(mid).get(), messagesService.getReply(mid, rid)).size()); assertEquals("number of subscribed users should match", 1, subscriptionService.getUsersSubscribedToComments( messagesService.getMessage(mid).get(), messagesService.getReply(mid, rid), true).size()); assertEquals("should be subscribed", "Subscribed to @" + user.getName(), commandsManager.processCommand(readerUser, "S @" + user.getName(), emptyUri) .getText()); List friends = userService.getUserFriends(readerUser.getUid()); assertEquals("number of friend users should match", 2, friends.size()); assertEquals("number of reader users should match", 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("should be second reply", expectedSecondReply, commandsManager.processCommand(user, "#" + mid + " yoyo", URI.create("https://static.juick.com/settings/facebook.png")).getText()); assertEquals("should be third reply", 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()); Timestamp lastreply = jdbcTemplate.queryForObject("SELECT lastmessage FROM users WHERE id=?", Timestamp.class, user.getUid()); assertThat(lastreply.toInstant(), equalTo(reply.getCreated())); assertEquals("should be reply to second comment", 2, reply.getReplyto()); assertThat(commandsManager.processCommand(readerUser, "#" + mid + " *yo *there", emptyUri) .getText(), startsWith("Reply posted")); assertEquals("tags should be updated", "Tags are updated", commandsManager.processCommand(user, "#" + mid + " *there", emptyUri).getText()); assertEquals("number of tags should match", 2, tagService.getMessageTags(mid).size()); assertThat(messagesService.getMessage(mid).get().getTags().size(), is(2)); assertEquals("should be blacklisted", "Tag added to your blacklist", commandsManager.processCommand(readerUser, "BL *there", emptyUri).getText()); assertEquals("number of subscribed users should match", 0, subscriptionService.getSubscribedUsers(user.getUid(), msg2).size()); assertEquals("tags should be updated", "Tags are updated", commandsManager.processCommand(user, "#" + mid + " *there", emptyUri).getText()); assertEquals("number of tags should match", 1, tagService.getMessageTags(mid).size()); User taggerUser = userService.createUser("dummyTagger", "dummySecret").orElseThrow(IllegalStateException::new); assertEquals("should be subscribed", "Subscribed", commandsManager.processCommand(taggerUser, "S *yo", emptyUri).getText()); assertEquals("number of subscribed users should match", 2, subscriptionService.getSubscribedUsers(user.getUid(), msg2).size()); assertEquals("should be unsubscribed", "Unsubscribed from yo", commandsManager.processCommand(taggerUser, "U *yo", emptyUri).getText()); assertEquals("number of subscribed users should match", 1, subscriptionService.getSubscribedUsers(user.getUid(), msg2).size()); assertEquals("number of readers should match", 1, userService.getUserReaders(user.getUid()).size()); String readerFeed = commandsManager.processCommand(readerUser, "#", emptyUri).getText(); assertTrue("description should match", readerFeed.startsWith("Your feed")); assertEquals("should be unsubscribed", "Unsubscribed from @" + user.getName(), commandsManager.processCommand(readerUser, "U @" + user.getName(), emptyUri) .getText()); assertEquals("number of readers should match", 0, userService.getUserReaders(user.getUid()).size()); assertEquals("number of friends should match", 1, userService.getUserFriends(user.getUid()).size()); assertEquals("should be unsubscribed", "Unsubscribed from #" + mid, commandsManager.processCommand(readerUser, "u #" + mid, emptyUri).getText()); assertEquals("number of subscribed users should match", 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("should be deleted", "Message deleted", commandsManager.processCommand(user, "D #" + mid, emptyUri).getText()); assertEquals("should be not found", "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("expected single tag", 1, codeAndTagsTags.size()); assertEquals("the single tag should be the 'code'", "code", codeAndTagsTags.stream().findFirst().get().getName()); assertEquals("and the message should be with a C-code and without tags", 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); assertTrue(result.getNewMessage().isPresent()); 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, Collections.emptyList()); 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(juickName, juickPassword)).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(juickName, juickPassword)).content(reply)) .andExpect(status().isOk()); } @Test public void recommendTests() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "to be liked", null, null); String freefdHash = userService.getHashByUID(freefd.getUid()); int freefdMid = messagesService.createMessage(freefd.getUid(), "to be not liked", null, null); 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()); } @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, new ArrayList<>()); mockMvc.perform(post("/api/react?mid=" + mid1 + "&hash=" + freefdHash + "&reactionId=2")) .andExpect(status().isOk()); Message msg4 = messagesService.getMessage(mid1).get(); assertThat(msg4.getLikes(), is(0)); assertThat(messagesService.getMessages(AnonymousUser.INSTANCE, Collections.singletonList(mid1)).get(0).getLikes(), is(0)); Assert.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().getLikes(), is(1)); } @Test 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, null); 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(juick, 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", Collections.singletonList(banned)); privacyQueriesService.blacklistTag(freefd, banned); assertTrue(messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getAll(freefd.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); assertFalse(messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getAll(ugnich.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); assertTrue(messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getPhotos(freefd.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); assertFalse(messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getPhotos(ugnich.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); jdbcTemplate.update("UPDATE messages SET popular=1 WHERE message_id=?", mid); assertTrue(messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getPopular(freefd.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); assertFalse(messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getPopular(ugnich.getUid(), 0)) .stream().noneMatch(m -> m.getTags().contains(banned))); assertTrue(messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getMyFeed(freefd.getUid(), 0, true)) .stream().noneMatch(m -> m.getTags().contains(banned))); User newUser1 = userService.createUser("newUser1", "12345").orElseThrow(IllegalStateException::new); int newMid = messagesService.createMessage(newUser1.getUid(), "people", null, Collections.singletonList(banned)); messagesService.recommendMessage(newMid, ugnich.getUid()); assertTrue(messagesService.getMessages(AnonymousUser.INSTANCE, messagesService.getMyFeed(freefd.getUid(), 0, true)) .stream().noneMatch(m -> m.getTags().contains(banned))); tagService.updateTags(newMid, Collections.singletonList(banned)); assertThat(messagesService.getMessage(newMid).get().getTags().size(), is(0)); privacyQueriesService.blacklistUser(freefd, newUser1); assertTrue(messagesService.getMyFeed(freefd.getUid(), 0, true) .stream().noneMatch(m -> m == newMid)); } @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) { } @Override public Jid getConnectedResource() { return null; } }; 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)) .collect(Collectors.toList()); assertEquals("First tag must be", "test", tagList.get(0).getName()); assertEquals("Third tag must be", "test 3", tagList.get(2).getName()); assertEquals("Count of tags must be", 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("date should be in timestamp field", 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("date should be in ts field", 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(juickName, juickPassword))) .andExpect(status().isBadRequest()); } @Test public void attachmentSizeTests() throws IOException { ImageUtils imageUtils = new ImageUtils(StringUtils.EMPTY, StringUtils.EMPTY); Attachment attachment = imageUtils.getAttachment(new File(invisiblePixel.getURI())); assertThat(attachment.getHeight(), is(1)); assertThat(attachment.getWidth(), is(1)); } @Test public void meContainsAllInfo() throws Exception { jdbcTemplate.update("DELETE FROM subscr_users"); 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(juick, String.format("#%d/1 ban for a hour!", mid), emptyUri); commandsManager.processCommand(juick, 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)); commandsManager.processCommand(juick, 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); messagesService.createMessage(freefd.getUid(), "sux", null, Collections.singletonList(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)); commandsManager.processCommand(ugnich, "BL @freefd", emptyUri); } @Test public void cmykJpegShouldBeProcessedCorrectly() throws Exception { 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 { 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 { 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 { 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(activityPubManager.messageUri(original.getMid(), 0), is(updateEvent.getMessageUri())); 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())); messagesService.deleteMessage(ugnich.getUid(), original.getMid()); } @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, Collections.singletonList(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, null); 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, null); 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 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, null); 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") .param("username", userName) .param("password", userPassword)).andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login?error=1")); } @Test public void bannedUserShouldBeShadowedFromRecommendationsList() throws IOException { 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, null); 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().getLikes(), is(4)); assertThat(CollectionUtils.isEqualCollection(messagesService.getMessagesRecommendations( Collections.singletonList(mid)) .stream().map(p -> p.getRight()).map(User::getName).collect(Collectors.toList()), Arrays.asList("fmap", "ermine", "pogo", "Anonymous")), is(true)); privacyQueriesService.blacklistUser(userService.getUserByName("monstreek"), userService.getUserByName("pogo")); assertThat(messagesService.getMessage(mid).get().getLikes(), is(4)); assertThat(CollectionUtils.isEqualCollection(messagesService.getMessagesRecommendations( Collections.singletonList(mid)) .stream().map(p -> p.getRight()).map(User::getName).collect(Collectors.toList()), Arrays.asList("fmap", "ermine", "Anonymous")), is(true)); } @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, Collections.emptyList()); 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, Collections.emptyList()); 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()); } @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, null)) .collect(Collectors.toCollection(ArrayDeque::new)).descendingIterator()); List midsPage = mids.stream().limit(20).collect(Collectors.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, null); 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, null); 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)); } @Test public void userShouldNotSeeReplyButtonToBannedUser() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "freefd bl me", null, null); messagesService.createReply(mid, 0, ugnich, "yo", null); MvcResult loginResult = mockMvc.perform(post("/login") .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, null); 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", null); 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") .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, null); int midNew = messagesService.createMessage(ugnich.getUid(), "Я более новый Угнич", null, null); MvcResult loginResult = mockMvc.perform(post("/login") .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, null)).boxed().collect(Collectors.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") .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?redirect=false").cookie(rememberMeFromForm)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login/success")); } @Test public void anythingRedirects() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "yo", null, null); 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") .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, null); 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("/v2/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") .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 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); String undoFollower = (String) ((Map) undo.getObject()).get("object"); String createJsonStr = IOUtils.toString(new ClassPathResource("create.json").getURI(), StandardCharsets.UTF_8); Create create = jsonMapper.readValue(createJsonStr, Create.class); Map note = (Map) create.getObject(); Map attachmentObj = (Map) ((List) note.get("attachment")).get(0); String attachment = attachmentObj != null ? (String) attachmentObj.get("url") : 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", "", null); 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 = (String) ((Map) undoPleroma.getObject()).get("object"); 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, "*shit happens", emptyUri).getNewMessage().get(); Note note = activityPubManager.makeNote(msg); json = jsonMapper.writeValueAsString(Context.build(note)); Note replyNote = new Note(); replyNote.setId("http://localhost:8080/n/2-1"); replyNote.setInReplyTo(activityPubManager.messageUri(msg)); replyNote.setAttributedTo("http://localhost:8080/u/freefd"); replyNote.setTo(Collections.singletonList(activityPubManager.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((Person) signatureManager.getContext(URI.create("http://localhost:8080/u/freefd")).get(), (Person) 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())))); } @Test public void signingSpec() throws IOException { Person from = (Person) signatureManager.getContext(URI.create("http://localhost:8080/u/freefd")).get(); Person to = (Person) signatureManager.getContext(URI.create("http://localhost:8080/u/ugnich")).get(); Follow follow = new Follow(); follow.setActor("http://localhost:8080/u/freefd"); follow.setObject("http://localhost:8080/u/ugnich"); signatureManager.post(from, to, follow); } @Test 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"; Person ugnichPerson = profileController.getUser("ugnich"); now = Instant.now(); requestDate = DateFormattersHolder.getHttpDateFormatter().format(now); String signatureString = signatureManager.addSignature(ugnichPerson, testHost, "GET", meUri, requestDate); 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"); MockRestServiceServer restServiceServer = MockRestServiceServer.createServer(apClient); restServiceServer.expect(times(3), requestTo(testuserUri)) .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON)); restServiceServer.expect(times(3), requestTo(testuserkeyUri)) .andRespond(withSuccess(testuserResponseString, MediaType.APPLICATION_JSON)); Person testuser = (Person) signatureManager.getContext(testuserUri).get(); Assert.assertThat(testuser.getPublicKey().getPublicKeyPem(), is(testKeystoreManager.getPublicKeyPem())); Instant now2 = Instant.now(); String testRequestDate = DateFormattersHolder.getHttpDateFormatter().format(now2); String inboxUri = "/api/inbox"; String testSignatureString = signatureManager.addSignature(testuser, testHost, "POST", inboxUri, testRequestDate, testKeystoreManager); mockMvc.perform(post(inboxUri) .header("Host", testHost) .header("Date", testRequestDate) .header("Signature", testSignatureString) .contentType(Context.LD_JSON_MEDIA_TYPE) .content(IOUtils.toByteArray(testfollowRequest.getInputStream()))) .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()); apClient.setRequestFactory(originalRequestFactory); } @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", "", null); 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.update("INSERT INTO jids(loginhash, jid) VALUES('1', 'test@jid.tld')"); MvcResult formLoginResult = mockMvc.perform(post("/login") .param("username", ugnichName) .param("password", ugnichPassword)) .andExpect(status().is3xxRedirection()).andReturn(); Cookie rememberMeFromForm = formLoginResult.getResponse().getCookie("juick-remember-me"); mockMvc.perform(post("/signup") .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"); jdbcTemplate.update("UPDATE messages SET popular=1 where message_id=?", msg.getMid()); List top = messagesService.getMessages(ugnich, messagesService.getPopular(ugnich.getUid(), 0)); assertThat(top.size(), is(1)); } @Test public void verifiedUsersTest() { assertThat(userService.getUserByName("ugnich").isVerified(), is(false)); jdbcTemplate.update("INSERT INTO facebook(user_id, fb_id) VALUES(?, ?)", ugnich.getUid(), "100001866137681"); assertThat(userService.getUserByName("ugnich").isVerified(), is(true)); } @Test public void changeProfileOverApi() throws Exception { 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("avatar", 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)); 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 federatedUserDeletionFlow() 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((String) delete.getObject())) .andRespond(withStatus(HttpStatus.GONE)); restServiceServer.expect(requestTo((String) delete.getObject())) .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); } @Test 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((String) delete.getObject())) .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, Collections.emptyList()); 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); Map note = (Map) create.getObject(); String markdown = remarkConverter.convertFragment((String) note.get("content")); String commandBody = note.get("attachment") == null ? markdown : ((List) note.get("attachment")).stream().map(attachmentObj -> { Map attachment = (Map) attachmentObj; String attachmentUrl = attachment.get("url"); String attachmentName = attachment.get("name"); return PlainTextFormatter.markdownUrl(attachmentUrl, attachmentName); }).reduce(markdown, (current, next) -> String.format("%s\n%s", current, next)); } @Test public void hubzillaActor() 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")); } @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, null); 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")); } @Test public void wsThreadsShouldRedirect() throws Exception { int mid = messagesService.createMessage(ugnich.getUid(), "tst", null, Collections.emptyList()); 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(juick, 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, "YO", emptyUri); var mid = result.getNewMessage().get().getMid(); jdbcTemplate.update("UPDATE messages SET readonly=1 WHERE message_id=?", mid); 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)); } @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); Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); pemConverter.setProvider("BC"); final Reader pemReader = new InputStreamReader(new ByteArrayInputStream(clientSecretGenerator.getPemData())); final PEMParser parser = new PEMParser(pemReader); PrivateKey privateKey; Object pemObj = parser.readObject(); privateKey = pemConverter.getPrivateKey((PrivateKeyInfo) pemObj); // Generate public key from private key KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC"); ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1"); ECPoint Q = ecSpec.getG().multiply(((ECPrivateKey)privateKey).getD()); byte[] publicDerBytes = Q.getEncoded(false); ECPoint point = ecSpec.getCurve().decodePoint(publicDerBytes); ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, ecSpec); ECPublicKey publicKeyGenerated = (ECPublicKey) keyFactory.generatePublic(pubSpec); Jws jwt = Jwts.parserBuilder() .setSigningKey(publicKeyGenerated).build() .parseClaimsJws(secret); Assert.assertThat(jwt.getHeader().get("kid"), is("keyid")); Assert.assertThat(jwt.getHeader().get("alg"), is("ES256")); Claims claims = jwt.getBody(); Assert.assertThat(claims.get("iss"), is("teamid")); Assert.assertThat(claims.get("sub"), is("com.example.app")); Assert.assertThat(claims.get("aud"), is("https://appleid.apple.com")); } @Test public void adminsTest() throws Exception { assertThat(userService.isAdminUser(ugnich), is(true)); assertThat(userService.isAdminUser(freefd), is(false)); MvcResult formLoginResult = mockMvc.perform(post("/login") .param("username", ugnichName) .param("password", ugnichPassword)) .andExpect(status().is3xxRedirection()).andReturn(); Cookie ugnichLogin = formLoginResult.getResponse().getCookie("juick-remember-me"); formLoginResult = mockMvc.perform(post("/login") .param("username", freefdName) .param("password", freefdPassword)) .andExpect(status().is3xxRedirection()).andReturn(); Cookie freefdLogin = formLoginResult.getResponse().getCookie("juick-remember-me"); mockMvc.perform(get("/actuator/health") .cookie(ugnichLogin)) .andExpect(status().isOk()); mockMvc.perform(get("/actuator/health") .cookie(freefdLogin)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://localhost/login")); } }