/* * #%L * FlatPack Demonstration Client * %% * Copyright (C) 2012 Perka Inc. * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package com.getperka.flatpack.demo.client; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigDecimal; import java.net.HttpURLConnection; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; import javax.ws.rs.core.UriBuilder; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import com.getperka.flatpack.Configuration; import com.getperka.flatpack.FlatPack; import com.getperka.flatpack.FlatPackEntity; import com.getperka.flatpack.client.StatusCodeException; import com.getperka.flatpack.demo.server.DemoServer; /** * Contains a variety of smoke tests to demonstrate various facets of the FlatPack stack. */ public class ClientSmokeTest { private static final int PORT = 8111; /** * Spin up an instance of the demo server. */ @BeforeClass public static void beforeClass() { assertTrue(new DemoServer().start(PORT)); } private ClientApi api; private Random random; /** * Creates an instance of the ClientApi. */ @Before public void before() throws IOException { random = new Random(0); Configuration config = new Configuration() .addTypeSource(ClientTypeSource.get()); api = new ClientApi(FlatPack.create(config)); api.setServerBase(UriBuilder.fromUri("http://localhost").port(PORT).build()); api.setVerbose(true); assertEquals(204, api.resetPost().execute().getResponseCode()); } /** * Demonstrates ConstraintViolation handling. ConstraintViolations are returned as a simple * {@code string:string} map in the FlatPackEntity's {@code error} block. The goal is to provide * enough context for user interfaces to present the error message in a useful way, without * assuming that the client is a Java app. */ @Test public void testConstraintViolation() throws IOException { Product p = makeProduct(); p.setPrice(BigDecimal.valueOf(-1)); FlatPackEntity<?> entity = null; try { api.productsPut(Collections.singletonList(p)) .queryParameter("isAdmin", "true") .execute(); fail("Should have seen StatusCodeException"); } catch (StatusCodeException e) { // The 400 status code is returned by the service method. assertEquals(400, e.getStatusCode()); /* * If the server returned a valid flatpack-encoded response, it can be retrieved from the * StatusCodeException. Otherwise, this method will return null. */ entity = e.getEntity(); } // Pull out the error messages Map<String, String> errors = entity.getExtraErrors(); assertNotNull(errors); assertEquals(1, errors.size()); Map.Entry<String, String> entry = errors.entrySet().iterator().next(); assertEquals("price", entry.getKey()); assertEquals("must be greater than or equal to 0", entry.getValue()); } /** * Demonstrates how generated client API will present a non-FlatPack resource (as an * HttpUrlConnection). */ @Test public void testNonFlatpackEndpoint() throws IOException { // The query parameters are added as a builder-style pattern HttpURLConnection conn = api.helloGet().withName("ClientSmokeTest").execute(); assertEquals(200, conn.getResponseCode()); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), Charset.forName("UTF8"))); assertEquals("Hello ClientSmokeTest!", reader.readLine()); } /** * Demonstrates the use of roles to restrict property setters. */ @Test public void testRolePropertyAccess() throws IOException { // Create a Product Product p = new Product(); UUID uuid = p.getUuid(); p.setName("Product"); p.setNotes("Some notes"); p.setPrice(BigDecimal.valueOf(42)); api.productsPut(Collections.singletonList(p)) .queryParameter("isAdmin", "true") .execute(); // Try to update it with a non-admin request p = new Product(); p.setUuid(uuid); p.setPrice(BigDecimal.valueOf(1)); api.productsPut(Collections.singletonList(p)).execute(); // Verify that nothing changed, as nobody List<Product> products = api.productsGet().execute().getValue(); assertEquals(1, products.size()); p = products.get(0); // Same UUID assertEquals(uuid, p.getUuid()); // Unchanged price assertEquals(BigDecimal.valueOf(42), p.getPrice()); // Can't see the notes assertNull(p.getNotes()); // Now try the update again, as an admin p = new Product(); p.setUuid(uuid); p.setPrice(BigDecimal.valueOf(99)); api.productsPut(Collections.singletonList(p)) .queryParameter("isAdmin", "true") .execute(); // Verify the changes, as nobody products = api.productsGet().execute().getValue(); assertEquals(1, products.size()); p = products.get(0); // Same UUID assertEquals(uuid, p.getUuid()); // Unchanged price assertEquals(BigDecimal.valueOf(99), p.getPrice()); } /** * Demonstrates a couple of round-trips to the server. */ @Test public void testSimpleGetAndPut() throws IOException { List<Product> products = api.productsGet().execute().getValue(); assertEquals(0, products.size()); // A server error would be reported as a StatusCodeException, a subclass of IOException api.productsPut(Arrays.asList(makeProduct(), makeProduct())) .queryParameter("isAdmin", "true") .execute(); /* * The object returned from productsGet() is an endpoint-specific interface that may contain * additional fluid setters for declared query parameters. It also provides access to some * request internals, including the FlatPackEntity that will be sent as part of the request. * This allows callers to further customize outgoing requests, in the above case to add the * isAdmin query parameter that interacts with the DummyAuthenticator. The call to execute() * triggers payload serialization and execution of the HTTP request. This returns a * FlatPackEntity describing the response, and getValue() returns the primary value object * contained in the payload. */ FlatPackEntity<List<Product>> entity = api.productsGet().execute(); assertTrue(entity.getExtraErrors().toString(), entity.getExtraErrors().isEmpty()); assertTrue(entity.getExtraWarnings().toString(), entity.getExtraWarnings().isEmpty()); products = entity.getValue(); assertEquals(2, products.size()); assertEquals(BigDecimal.valueOf(360), products.get(0).getPrice()); assertEquals(BigDecimal.valueOf(948), products.get(1).getPrice()); assertTrue(products.get(0).wasPersistent()); assertTrue(products.get(1).wasPersistent()); // Try to update one of the objects Product p = products.get(0); p.setPrice(BigDecimal.valueOf(99)); assertEquals(Collections.singleton("price"), p.dirtyPropertyNames()); api.productsPut(Collections.singletonList(p)).queryParameter("isAdmin", "true").execute(); // Re-fetch and verify update products = api.productsGet().execute().getValue(); assertEquals(99, products.get(0).getPrice().intValue()); assertTrue(products.get(0).dirtyPropertyNames().isEmpty()); } private Product makeProduct() { Product p = new Product(); p.setName("ClientSmokeTest"); p.setPrice(BigDecimal.valueOf(random.nextInt(1000))); return p; } }