View Javadoc

1   package it.com.atlassian.plugins.rest.sample.entities;
2   
3   import com.atlassian.plugins.rest.sample.entities.EntityClientServlet;
4   import com.atlassian.plugins.rest.sample.entities.EntityResource;
5   import com.atlassian.plugins.rest.sample.entities.UriBuilder;
6   import com.google.common.io.ByteStreams;
7   import com.sun.jersey.api.client.Client;
8   import org.apache.commons.httpclient.HttpClient;
9   import org.apache.commons.httpclient.methods.PostMethod;
10  import org.apache.commons.httpclient.methods.RequestEntity;
11  import org.apache.commons.httpclient.methods.StringRequestEntity;
12  import org.hamcrest.CoreMatchers;
13  import org.hamcrest.Matchers;
14  import org.junit.Test;
15  import org.junit.matchers.JUnitMatchers;
16  
17  import javax.ws.rs.core.MediaType;
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.URI;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  
25  import static com.atlassian.core.util.ClassLoaderUtils.getResourceAsStream;
26  import static com.atlassian.plugins.rest.sample.entities.EntityClientServlet.P_ACCEPT;
27  import static com.atlassian.plugins.rest.sample.entities.EntityClientServlet.P_CONTENT_TYPE;
28  import static com.atlassian.plugins.rest.sample.entities.EntityClientServlet.P_ORANGE;
29  import static org.junit.Assert.assertEquals;
30  import static org.junit.Assert.assertNotNull;
31  import static org.junit.Assert.assertThat;
32  
33  /**
34   * Testing {@link EntityResource}
35   */
36  public class EntityTest {
37      /* A secret that shouldn't be exposed to anonymous requests */
38      private static final String SECRET = Long.toString(Double.doubleToLongBits(Math.random()));
39  
40      protected File createSecretFile() throws IOException {
41          final Path path = Files.createTempFile("secret-file", ".txt");
42          Files.write(path, SECRET.getBytes());
43          File f = path.toFile();
44          f.deleteOnExit();
45          return f;
46      }
47  
48      /**
49       * Simple test that executes {@link EntityClientServlet}, which uses the {@link com.atlassian.sal.api.net.RequestFactory} to query
50       * {@link EntityResource}
51       */
52      @Test
53      public void testApplesForOranges() {
54          final URI baseUri = UriBuilder.create().path("plugins").path("servlet").path("client").build();
55          final String returned = Client.create().resource(baseUri).queryParam(P_ORANGE, "valencia").get(String.class);
56          assertEquals(
57                  "apple-valencia\n" +
58                          "Content-Type=application/xml\n" +
59                          "Accept=application/xml", returned);   // application/xml is the default content-type & accept header value
60      }
61  
62      @Test
63      public void testApplesForOrangesJson() {
64          final URI baseUri = UriBuilder.create().path("plugins").path("servlet").path("client").build();
65          final String returned = Client.create().resource(baseUri)
66                  .queryParam(P_ORANGE, "delfino")
67                  .queryParam(P_CONTENT_TYPE, "application/json")
68                  .queryParam(P_ACCEPT, "application/json")
69                  .get(String.class);
70          assertEquals(
71                  "apple-delfino\n" +
72                          "Content-Type=application/json\n" +
73                          "Accept=application/json", returned);
74      }
75  
76      @Test
77      public void testApplesForOrangesDefaultContentTypeAcceptJson() {
78          final URI baseUri = UriBuilder.create().path("plugins").path("servlet").path("client").build();
79          final String returned = Client.create().resource(baseUri)
80                  .queryParam(P_ORANGE, "delfino")
81                  .queryParam(P_ACCEPT, "application/json")
82                  .get(String.class);
83          assertEquals(
84                  "apple-delfino\n" +
85                          "Content-Type=application/xml\n" +
86                          "Accept=application/json", returned);
87      }
88  
89      @Test
90      public void testApplesForOrangesExplicitXmlContentTypeAndAccept() {
91          final URI baseUri = UriBuilder.create().path("plugins").path("servlet").path("client").build();
92          final String returned = Client.create().resource(baseUri)
93                  .queryParam(P_ORANGE, "delfino")
94                  .queryParam(P_CONTENT_TYPE, "application/xml")
95                  .queryParam(P_ACCEPT, "application/xml")
96                  .get(String.class);
97          assertEquals(
98                  "apple-delfino\n" +
99                          "Content-Type=application/xml\n" +
100                         "Accept=application/xml", returned);
101     }
102 
103     @Test
104     public void testApplesForOrangesMissingAcceptHeaderDefaultsToContentType() {
105         final URI baseUri = UriBuilder.create().path("plugins").path("servlet").path("client").build();
106         final String returned = Client.create().resource(baseUri)
107                 .queryParam(P_ORANGE, "delfino")
108                 .queryParam(P_CONTENT_TYPE, "application/json")
109                 .get(String.class);
110         assertEquals(
111                 "apple-delfino\n" +
112                         "Content-Type=application/json\n" +
113                         "Accept=application/json", returned);
114     }
115 
116     @Test
117     public void testApplesForOrangesMultipleValidAcceptHeaders() {
118         final URI baseUri = UriBuilder.create().path("plugins").path("servlet").path("client").build();
119         final String returned = Client.create().resource(baseUri)
120                 .queryParam(P_ORANGE, "delfino")
121                 .queryParam(P_ACCEPT, "application/json")
122                 .queryParam(P_ACCEPT, "application/xml")
123                 .get(String.class);
124         assertEquals(
125                 "apple-delfino\n" +
126                         "Content-Type=application/xml\n" + //default
127                         "Accept=application/json,application/xml", returned);
128     }
129 
130 
131     private PostMethod postToRestEndpoint(String content) throws IOException {
132         RequestEntity re = new StringRequestEntity(content, "application/xml", "us-ascii");
133 
134         final URI baseUri = UriBuilder.create().path("rest").path("entity").path("latest").path("fruit").build();
135 
136         HttpClient client = new HttpClient();
137 
138         PostMethod m = new PostMethod(baseUri.toString());
139 
140         m.setRequestHeader("Accept", "application/xml");
141         m.setRequestEntity(re);
142 
143         client.executeMethod(m);
144 
145         return m;
146     }
147 
148     @Test
149     public void testEntityExpansionDoesNotIncludeFileContents() throws IOException {
150         InputStream in = getResourceAsStream("EntityTest-rest-include-external-entity.xml", this.getClass());
151         assertNotNull(in);
152 
153         String contents = new String(ByteStreams.toByteArray(in), "us-ascii");
154 
155         contents = contents.replace("/etc/passwd", createSecretFile().toURI().toString());
156 
157         PostMethod m = postToRestEndpoint(contents);
158 
159         String resp = new String(ByteStreams.toByteArray(m.getResponseBodyAsStream()), "us-ascii");
160 
161         MediaType mt = MediaType.valueOf(m.getResponseHeader("content-type").getValue());
162 
163         assertEquals("The response should be XML",
164                 "application/xml",
165                 mt.getType() + '/' + mt.getSubtype()
166         );
167         assertThat(resp, CoreMatchers.not(JUnitMatchers.containsString(SECRET)));
168     }
169 
170     @Test
171     public void testValidEntitiesAreExpanded() throws IOException {
172         InputStream in = getResourceAsStream("EntityTest-rest-with-amp-entity.xml", this.getClass());
173         assertNotNull(in);
174 
175         String contents = new String(ByteStreams.toByteArray(in), "us-ascii");
176 
177         PostMethod m = postToRestEndpoint(contents);
178 
179         String resp = new String(ByteStreams.toByteArray(m.getResponseBodyAsStream()), "us-ascii");
180 
181         MediaType mt = MediaType.valueOf(m.getResponseHeader("content-type").getValue());
182 
183         assertEquals("The response should be XML",
184                 "application/xml",
185                 mt.getType() + '/' + mt.getSubtype()
186         );
187         assertThat(resp, JUnitMatchers.containsString("apple-&"));
188     }
189 
190     @Test
191     public void testEntityExpansionDoesNotCauseDenialOfService() throws IOException {
192         InputStream in = getResourceAsStream("EntityTest-rest-billion-laughs.xml", this.getClass());
193         assertNotNull(in);
194 
195         String contents = new String(ByteStreams.toByteArray(in), "us-ascii");
196 
197         PostMethod m = postToRestEndpoint(contents);
198 
199         String resp = new String(ByteStreams.toByteArray(m.getResponseBodyAsStream()), "us-ascii");
200 
201         // For this deployment this means a simple syntax error, in some environments you will actually get a OOME if this fails.
202         // REST doesn't send any content with UnmarshalException, so any content arrives is a status code template
203         // of the web server used which is not stable. The only reliable indicator of behavior is the status code 
204         // which should indicate a bad request rather than a server error
205         assertThat("The response should not indicate a server error",
206                 m.getStatusCode(), Matchers.is(400));
207 
208         // TODO [pandronov]: That is completely useless check. EOM like any other JVM Error can't be
209         // catched in any reliable way and JVM behavior doens't define after an Error. In other words
210         // there is no any guarantee to get anything that makes sense if it was an Error on server
211         // side....
212         assertThat("The response should not indicate a server memory error",
213                 resp, CoreMatchers.not(JUnitMatchers.containsString("java.lang.OutOfMemoryError")));
214     }
215 
216     @Test
217     public void fruitRetrievedAsXmlObeysJaxbAnnotations() {
218         final URI baseUri = UriBuilder.create().path("rest/entity/1/fruit/jackfruit").build();
219         final String returned = Client.create().resource(baseUri)
220                 .accept(MediaType.APPLICATION_XML)
221                 .get(String.class);
222 
223         assertThat(returned, Matchers.containsString("<jackFruit jaxb-description=\""));
224     }
225 
226     @Test
227     public void fruitRetrievedAsJsonObeysJacksonAnnotations() {
228         final URI baseUri = UriBuilder.create().path("rest/entity/1/fruit/jackfruit").build();
229         final String returned = Client.create().resource(baseUri)
230                 .accept(MediaType.APPLICATION_JSON)
231                 .get(String.class);
232 
233         assertThat(returned, Matchers.startsWith("{\"json-description\":\"fresh at"));
234     }
235 }