Java tutorial
// ---------------------------------------------------------------------------- // This file is part of the Kasper framework. // // The Kasper framework is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // Kasper framework 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the framework Kasper. // If not, see <http://www.gnu.org/licenses/>. // -- // Ce fichier fait partie du framework logiciel Kasper // // Ce programme est un logiciel libre ; vous pouvez le redistribuer ou le // modifier suivant les termes de la GNU Lesser General Public License telle // que publie par la Free Software Foundation ; soit la version 3 de la // licence, soit ( votre gr) toute version ultrieure. // // Ce programme est distribu dans l'espoir qu'il sera utile, mais SANS // AUCUNE GARANTIE ; sans mme la garantie tacite de QUALIT MARCHANDE ou // d'ADQUATION UN BUT PARTICULIER. Consultez la GNU Lesser General Public // License pour plus de dtails. // // Vous devez avoir reu une copie de la GNU Lesser General Public License en // mme temps que ce programme ; si ce n'est pas le cas, consultez // <http://www.gnu.org/licenses> // ---------------------------------------------------------------------------- // ============================================================================ // KASPER - Kasper is the treasure keeper // www.viadeo.com - mobile.viadeo.com - api.viadeo.com - dev.viadeo.com // // Viadeo Framework for effective CQRS/DDD architecture // ============================================================================ package com.viadeo.kasper.exposition.http.jetty.resource; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; import com.google.common.base.Charsets; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.core.DefaultResourceConfig; import com.sun.jersey.test.framework.JerseyTest; import com.sun.jersey.test.framework.LowLevelAppDescriptor; import com.viadeo.kasper.api.component.event.Event; import com.viadeo.kasper.doc.element.DocumentedPlatform; import com.viadeo.kasper.doc.initializer.DefaultDocumentedElementInitializer; import com.viadeo.kasper.doc.web.ObjectMapperKasperResolver; import com.viadeo.kasper.domain.sample.applications.api.Applications; import com.viadeo.kasper.domain.sample.applications.command.model.entity.Application; import com.viadeo.kasper.domain.sample.applications.command.model.entity.Member_fanOf_Application; import com.viadeo.kasper.domain.sample.applications.command.repository.ApplicationMemberFansRepository; import com.viadeo.kasper.domain.sample.applications.command.repository.ApplicationRepository; import com.viadeo.kasper.domain.sample.root.api.Facebook; import com.viadeo.kasper.domain.sample.root.api.command.AddConnectionToMemberCommand; import com.viadeo.kasper.domain.sample.root.api.event.FacebookEvent; import com.viadeo.kasper.domain.sample.root.api.event.FacebookMemberEvent; import com.viadeo.kasper.domain.sample.root.api.event.MemberCreatedEvent; import com.viadeo.kasper.domain.sample.root.api.event.NewMemberConnectionEvent; import com.viadeo.kasper.domain.sample.root.api.query.GetMembersQueryHandler; import com.viadeo.kasper.domain.sample.root.command.handler.AddConnectionToMemberHandler; import com.viadeo.kasper.domain.sample.root.command.listener.MemberCreatedEventListener; import com.viadeo.kasper.domain.sample.root.command.model.entity.Member; import com.viadeo.kasper.domain.sample.root.command.model.entity.Member_connectedTo_Member; import com.viadeo.kasper.domain.sample.root.command.repository.MemberConnectionsRepository; import com.viadeo.kasper.domain.sample.root.command.repository.MemberRepository; import com.viadeo.kasper.domain.sample.timelines.api.Timelines; import com.viadeo.kasper.domain.sample.timelines.command.model.entity.Status; import com.viadeo.kasper.domain.sample.timelines.command.model.entity.Timeline; import com.viadeo.kasper.domain.sample.timelines.command.repository.StatusRepository; import com.viadeo.kasper.domain.sample.timelines.command.repository.TimelineRepository; import com.viadeo.kasper.platform.bundle.descriptor.*; import difflib.Delta; import difflib.DiffUtils; import difflib.Patch; import org.apache.commons.io.IOUtils; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.junit.Test; import org.reflections.Reflections; import org.reflections.scanners.ResourcesScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.Path; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * This class manage with automated testing of KasperDocumentation HTTP/JSON endpoints * * Add your JSON test response in src/test/resources/json * Just name it with the path ofthe resource to request, replacing slashes '/' by underscores '_' * Ex: * "json/domain_Facebook_concept_Member.json" * will request the kasper doc HTTP endpoint for /domain/Facebook/concept/Member and it will * apply a simple JSON comparison of the response to the contents of the json test file * */ public class KasperDocResourceTest extends JerseyTest { private static final Logger LOGGER = LoggerFactory.getLogger(KasperDocResourceTest.class); /** * Use with cautious : check all outputs unless you'll introduce regressions in tests * Set to true for ONLY ONE LAUNCH if you are sure of what you're doing, then switch it back to false */ private static final boolean UPDATE_TESTS = false; // ------------------------------------------------------------------------ /** * The wrapped resource, in order to not use injection in the Kasper doc resource * * Will delegate all calls to the standard Kasper doc root resource */ @Path("/") public static class WrappedDocResource { public WrappedDocResource() { } @Path("/") public KasperDocResource delegate() { final DomainDescriptor facebookDomainDescriptor = new DomainDescriptor(Facebook.NAME, Facebook.class, ImmutableList.<QueryHandlerDescriptor>of(new QueryHandlerDescriptor( GetMembersQueryHandler.class, GetMembersQueryHandler.GetMembersQuery.class, GetMembersQueryHandler.MembersResult.class)), ImmutableList.<CommandHandlerDescriptor>of( new CommandHandlerDescriptor(AddConnectionToMemberHandler.class, AddConnectionToMemberCommand.class)), ImmutableList.<RepositoryDescriptor>of( new RepositoryDescriptor(MemberRepository.class, DomainDescriptorFactory.toAggregateDescriptor(Member.class)), new RepositoryDescriptor(MemberConnectionsRepository.class, DomainDescriptorFactory .toAggregateDescriptor(Member_connectedTo_Member.class))), ImmutableList.<EventListenerDescriptor>of(new EventListenerDescriptor( MemberCreatedEventListener.class, MemberCreatedEvent.class)), ImmutableList.<SagaDescriptor>of(), ImmutableList.<Class<? extends Event>>of(FacebookEvent.class, FacebookMemberEvent.class, MemberCreatedEvent.class, NewMemberConnectionEvent.class)); final DomainDescriptor applicationDomainDescriptor = new DomainDescriptor(Applications.NAME, Applications.class, ImmutableList.<QueryHandlerDescriptor>of(), ImmutableList.<CommandHandlerDescriptor>of(), ImmutableList.<RepositoryDescriptor>of( new RepositoryDescriptor(ApplicationRepository.class, DomainDescriptorFactory.toAggregateDescriptor(Application.class)), new RepositoryDescriptor(ApplicationMemberFansRepository.class, DomainDescriptorFactory.toAggregateDescriptor(Member_fanOf_Application.class))), ImmutableList.<EventListenerDescriptor>of(), ImmutableList.<SagaDescriptor>of(), ImmutableList.<Class<? extends Event>>of()); final DomainDescriptor timelinesDomainDescriptor = new DomainDescriptor(Timeline.NAME, Timelines.class, ImmutableList.<QueryHandlerDescriptor>of(), ImmutableList.<CommandHandlerDescriptor>of(), ImmutableList.<RepositoryDescriptor>of( new RepositoryDescriptor(StatusRepository.class, DomainDescriptorFactory.toAggregateDescriptor(Status.class)), new RepositoryDescriptor(TimelineRepository.class, DomainDescriptorFactory.toAggregateDescriptor(Timeline.class))), ImmutableList.<EventListenerDescriptor>of(), ImmutableList.<SagaDescriptor>of(), ImmutableList.<Class<? extends Event>>of()); final DocumentedPlatform documentedPlatform = new DocumentedPlatform(); documentedPlatform.registerDomain(Facebook.NAME, facebookDomainDescriptor); documentedPlatform.registerDomain(Applications.NAME, applicationDomainDescriptor); documentedPlatform.registerDomain(Timelines.NAME, timelinesDomainDescriptor); documentedPlatform.accept(new DefaultDocumentedElementInitializer(documentedPlatform)); return new KasperDocResource(documentedPlatform); } } static class TestConfiguration extends DefaultResourceConfig { public TestConfiguration() { super(WrappedDocResource.class); getProviderSingletons().add(new JacksonJsonProvider(new ObjectMapperKasperResolver().getContext(null))); } } // ------------------------------------------------------------------------ public KasperDocResourceTest() { super(new LowLevelAppDescriptor.Builder(new TestConfiguration()).contextPath("/").build()); } // ------------------------------------------------------------------------ /** * Main run test method * * Scans all available json files, make the request to a standalone HTTP server and compare * expected and retrieved results * @throws URISyntaxException * * @throws Exception */ @Test public void test() throws IOException, JSONException, URISyntaxException { // Traverse available json responses ------------------------------------ final Predicate<String> filter = new FilterBuilder().include(".*\\.json"); final Reflections reflections = new Reflections( new ConfigurationBuilder().filterInputsBy(filter).setScanners(new ResourcesScanner()) .setUrls(Arrays.asList(ClasspathHelper.forClass(this.getClass())))); final Set<String> resolved = reflections.getResources(Pattern.compile(".*")); // Execute against Kasper documentation ------------------------------- boolean failed = false; for (final String jsonFilename : resolved) { LOGGER.info("** Test response file " + jsonFilename); final String json = getJson(jsonFilename); final String path = jsonFilename.replaceAll("json/", "").replaceAll("-", "/").replaceAll("\\.json", ""); final WebResource webResource = resource(); final String responseMsg = webResource.path("/" + path).get(String.class); try { assertJsonEquals(responseMsg, json); } catch (final JSONException e) { LOGGER.info("\t--> ERROR"); throw e; } catch (final AssertionError e) { if (!UPDATE_TESTS) { LOGGER.debug("*** RETURNED RESULT :"); LOGGER.debug(new JSONObject(responseMsg).toString(2)); LOGGER.info("\t--> ERROR"); failed = true; } } if (UPDATE_TESTS) { final URL url = ClassLoader.getSystemResource(jsonFilename); final String filename = url.getFile().replaceAll("target/test-classes", "src/test/resources"); LOGGER.info("\t--> SAVE to " + filename); final File file = new File(filename); final FileOutputStream fos = new FileOutputStream(file); fos.write(new JSONObject(responseMsg).toString(2).getBytes(Charsets.UTF_8)); fos.close(); } LOGGER.info("\t--> OK"); } if (failed) { fail(); } } // ------------------------------------------------------------------------ /** * Get the JSON contents of the given claspath resource * * @param jsonFilename the classpath resource relative location * @return the json file contents * @throws IOException */ private String getJson(final String jsonFilename) throws IOException { final InputStream jsonStream = ClassLoader.getSystemResourceAsStream(jsonFilename); if (null == jsonStream) { fail(String.format("Unable to find response file %s", jsonFilename)); } final String jsonString = IOUtils.toString(jsonStream, "UTF-8"); // Check jsonStream null jsonStream.close(); return jsonString.isEmpty() ? "{}" : jsonString; } // ------------------------------------------------------------------------ /** * Compare the content of two json files * The two contents are first normalized using org.json.JSONObject * Then raw strings are simply compared, after being cleaned of any space or carriage return occurences * * @param jsonA the first json string * @param jsonB the second json string * @throws JSONException if one json string is not parseable */ private void assertJsonEquals(final String jsonA, final String jsonB) throws JSONException { final String jsonAA; final String jsonBB; try { jsonAA = new JSONObject(jsonA).toString(2); } catch (final JSONException e) { LOGGER.debug("*** BAD JSON :"); LOGGER.debug(jsonA); throw e; } try { jsonBB = new JSONObject(jsonB).toString(2); } catch (final JSONException e) { LOGGER.debug("*** BAD JSON :"); LOGGER.debug(jsonB); throw e; } try { assertEquals(jsonAA.replaceAll("[\\s\\n]", ""), jsonBB.replaceAll("[\\s\\n]", "")); } catch (final AssertionError e) { LOGGER.debug("*** DIFF RESULT (RESPONSE vs EXPECTED) :"); new StringLinesDiffer().output(jsonBB, jsonAA); throw e; } } // ------------------------------------------------------------------------ /** * Outputs diff deltas between two String (splitted by lines) */ public class StringLinesDiffer { private List<String> stringToLines(final String data) { return Lists.newArrayList(data.split("\\r?\\n")); } public void output(final String strA, final String strB) { final List<String> original = stringToLines(strA); final List<String> revised = stringToLines(strB); final Patch patch = DiffUtils.diff(original, revised); for (Delta delta : patch.getDeltas()) { LOGGER.debug(delta.toString()); } } } }