package controllers; import static controllers.Helpers.callAction; import static helpers.FileStoreHelper.XLSX_MIME_TYPE; import static helpers.FileStoreHelper.XLS_MIME_TYPE; import static java.util.Arrays.asList; import static org.fest.assertions.Assertions.assertThat; import static play.test.Helpers.charset; import static play.test.Helpers.contentAsString; import static play.test.Helpers.contentType; import static play.test.Helpers.header; import static play.test.Helpers.status; import static test.AorraTestUtils.asAdminUser; import static test.AorraTestUtils.asAdminUserSession; import static test.AorraTestUtils.fileStore; import static test.AorraTestUtils.loggedInRequest; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.StringReader; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.UUID; import javax.jcr.RepositoryException; import javax.jcr.Session; import models.User; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.junit.Test; import org.supercsv.io.CsvListReader; import org.supercsv.io.ICsvListReader; import org.supercsv.prefs.CsvPreference; import play.api.mvc.Call; import play.libs.F; import play.libs.Json; import play.mvc.HandlerRef; import play.mvc.Http; import play.mvc.Result; import play.test.FakeRequest; import service.filestore.FileStore; import charts.representations.Format; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.google.common.base.Joiner; import com.google.common.collect.Lists; public class ChartTest { private static final String[] TTT_CHARTS = new String[] { "ttt_cane_and_hort","ttt_grazing","ttt_sediment","ttt_nitro_and_pest" }; private static final String[] LAND_PS_CHARTS = new String[] { "horticulture_ps","sugarcane_ps" }; @Test public void routes() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { { final String randomUUID = UUID.randomUUID().toString(); final Call call = controllers.routes.Chart.charts("svg", randomUUID); assertThat(call.method()).isEqualTo("GET"); assertThat(call.url()).isEqualTo(String.format( "/file/%s/charts?format=svg", randomUUID)); } { final String randomUUID = UUID.randomUUID().toString(); final Call call = controllers.routes.Chart.chart("marine", "svg", randomUUID); assertThat(call.method()).isEqualTo("GET"); assertThat(call.url()).isEqualTo(String.format( "/file/%s/charts/marine.svg", randomUUID)); } return session; } }); } @Test public void getCharts() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { final FileStore.File f = createMarineChartFile(session); final HandlerRef[] calls = new HandlerRef[] { controllers.routes.ref.Chart.charts("svg", f.getIdentifier()), }; for (HandlerRef call : calls) { final Result result = callAction(call, newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("application/json"); assertThat(charset(result)).isEqualTo("utf-8"); assertThat(header("Cache-Control", result)) .isEqualTo("max-age=0, must-revalidate"); final JsonNode json = Json.parse(contentAsString(result)); assertThat(json.has("charts")).isTrue(); assertThat(json.get("charts").isArray()).isTrue(); assertThat(json.get("charts")).hasSize(7); for (JsonNode chartJson : (ArrayNode) json.get("charts")) { assertThat(chartJson.isObject()).as("chart is object").isTrue(); assertThat(chartJson.has("type")).as("has type").isTrue(); assertThat(chartJson.get("type").asText()).isEqualTo("Marine"); assertThat(chartJson.has("region")).as("has region").isTrue(); assertThat(chartJson.has("url")).as("has region").isTrue(); assertThat(chartJson.get("url").asText()).contains("svg"); assertThat(chartJson.get("url").asText()) .contains(f.getIdentifier()); } } return session; } }); } @Test public void getNoCharts() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); final FileStore.File f = folder.createFile("test.txt", "text/plain", new ByteArrayInputStream("Test content.".getBytes())); // Try with file that isn't a spreadsheet { final Result result = callAction( controllers.routes.ref.Chart.charts("svg", f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("application/json"); assertThat(charset(result)).isEqualTo("utf-8"); assertThat(header("Cache-Control", result)) .isEqualTo("max-age=0, must-revalidate"); final JsonNode json = Json.parse(contentAsString(result)); assertThat(json.has("charts")).isTrue(); assertThat(json.get("charts").isArray()).isTrue(); assertThat(json.get("charts")).hasSize(0); } // Try with folder { final Result result = callAction( controllers.routes.ref.Chart.charts("svg", folder.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(404); } return session; } }); } @Test public void svgChart() { asAdminUser( new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { checkChart("marine", createMarineChartFile(session), newRequest); checkChart("marine", createMarineXlsChartFile(session), newRequest); checkChart("annual_rainfall", createAnnualRainfallChartFile(session), newRequest); checkChart("cots_outbreak", createCOTOutbreakChartFile(session), newRequest); checkChart("progress_table", createProgressTableChartFile(session), newRequest); checkChart("grazing_ps", createGrazingPracticeChartFile(session), newRequest); for (String c : LAND_PS_CHARTS) { checkChart(c, createLandPracticeChartFile(session, c), newRequest); } for (String c : TTT_CHARTS) { checkChart(c, createTrackingTowardsTargetsChartFile(session, c), newRequest); } return session; } private void checkChart( final String chartType, final FileStore.File f, final FakeRequest newRequest) { final HandlerRef[] calls = new HandlerRef[] { controllers.routes.ref.Chart.chart(chartType, "svg", f.getIdentifier()) }; for (HandlerRef call : calls) { Date d = new Date(); final Result result = callAction(call, newRequest); System.out.println(String.format("XXX chart (type %s) generation took %s ms", chartType, new Date().getTime()-d.getTime())); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("image/svg+xml"); } try { f.delete(); } catch(RepositoryException e) { throw new RuntimeException("while delete file with id "+f.getIdentifier(), e); } } }); asAdminUserSession( new F.Function3<Session, User, Http.Session, Session>() { @Override public Session apply( final Session session, final User user, final Http.Session httpSession) throws Throwable { checkChartWithRegions("marine", Arrays.asList("Cape York"), createMarineChartFile(session), httpSession); checkChartWithRegions("annual_rainfall", Arrays.asList("Fitzroy"), createAnnualRainfallChartFile(session), httpSession); return session; } private void checkChartWithRegions( final String chartType, final List<String> regions, final FileStore.File f, final Http.Session httpSession) { final List<String> pairs = Lists.newLinkedList(); for (String r : regions) { pairs.add("region="+r); } final String qs = Joiner.on("&").join(pairs); final Result result = callAction( controllers.routes.ref.Chart.chart(chartType, "svg", f.getIdentifier()), loggedInRequest(new FakeRequest("GET", "?"+qs), httpSession)); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("image/svg+xml"); try { f.delete(); } catch(RepositoryException e) { throw new RuntimeException("while delete file with id "+f.getIdentifier(), e); } } }); } @Test public void svgChartSize() { asAdminUserSession( new F.Function3<Session, User, Http.Session, Session>() { @Override public Session apply( final Session session, final User user, final Http.Session httpSession) throws Throwable { final FileStore.File f = createMarineChartFile(session); { final List<String> pairs = asList(); Element svg = getSvgRoot(httpSession, f.getIdentifier(), pairs); String[] viewBox = svg.attr("viewBox").split(" "); assertThat(svg.attr("width")).isEqualTo(viewBox[2]); assertThat(svg.attr("height")).isEqualTo(viewBox[3]); } { final List<String> pairs = asList("width=1337"); Element svg = getSvgRoot(httpSession, f.getIdentifier(), pairs); assertThat(svg.attr("width")).isEqualTo("1337"); assertThat(svg.attr("height")).isNotEqualTo("0"); } { final List<String> pairs = asList("height=1337"); Element svg = getSvgRoot(httpSession, f.getIdentifier(), pairs); assertThat(svg.attr("width")).isNotEqualTo("0"); assertThat(svg.attr("height")).isEqualTo("1337"); } { final List<String> pairs = asList("width=9832", "height=1337"); Element svg = getSvgRoot(httpSession, f.getIdentifier(), pairs); assertThat(svg.attr("width")).isEqualTo("9832"); assertThat(svg.attr("height")).isEqualTo("1337"); } return session; } private Element getSvgRoot( final Http.Session httpSession, final String fileId, final List<String> pairs) { final String qs = Joiner.on("&").join(pairs); final Result result = callAction( controllers.routes.ref.Chart.chart("marine", "svg", fileId), loggedInRequest(new FakeRequest("GET", "?"+qs), httpSession)); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("image/svg+xml"); final Document doc = Jsoup.parse(contentAsString(result)); return doc.select("svg").get(0); } }); } @Test public void unknownChartTypeOrFormat() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { final FileStore.File f = createMarineChartFile(session); { final Result result = callAction( controllers.routes.ref.Chart.chart("unknown", "svg", f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(404); } { final Result result = callAction( controllers.routes.ref.Chart.chart("marine", "unknown", f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(404); } return session; } }); } @Test public void otherChartFormats() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { private final Format[] OTHER_FORMATS = new Format[] { Format.PNG, Format.EMF }; @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { checkChart("marine", createMarineChartFile(session), newRequest); checkChart("marine", createMarineXlsChartFile(session), newRequest); return session; } private void checkChart( final String chartType, final FileStore.File f, final FakeRequest newRequest) { for (Format format : OTHER_FORMATS) { final Result result = callAction( controllers.routes.ref.Chart.chart(chartType, format.name(), f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo(format.getMimeType()); } } }); } @Test public void csvChart() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { // Marine chart CSV { final FileStore.File f = createMarineChartFile(session); final Result result = callAction( controllers.routes.ref.Chart.chart("marine", "csv", f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/csv"); assertThat(charset(result)).isEqualTo("utf-8"); final ICsvListReader listReader = new CsvListReader( new StringReader(contentAsString(result)), CsvPreference.STANDARD_PREFERENCE); int rowCount = 0; List<String> row; while ((row = listReader.read()) != null) { assertThat(row).hasSize(2); // GBR values are all MODERATE assertThat(row.get(1)).isEqualTo("Moderate"); rowCount++; } assertThat(rowCount).isEqualTo(13); listReader.close(); } // COT chart CSV { final FileStore.File f = createCOTOutbreakChartFile(session); final Result result = callAction( controllers.routes.ref.Chart.chart("cots_outbreak", "csv", f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/csv"); assertThat(charset(result)).isEqualTo("utf-8"); final ICsvListReader listReader = new CsvListReader( new StringReader(contentAsString(result)), CsvPreference.STANDARD_PREFERENCE); int rowCount = 0; List<String> row = listReader.read(); assertThat(row).contains("Year", "Outbreaks"); while ((row = listReader.read()) != null) { assertThat(row).hasSize(2); // COT years start at 1998 assertThat(row.get(0)).isEqualTo((1998+rowCount)+""); // COT outbreaks start at 29, decreasing by two each year assertThat(row.get(1)).isEqualTo((29-(2*rowCount))+""); rowCount++; } assertThat(rowCount).isEqualTo(12); listReader.close(); } // Rainfall { final FileStore.File f = createAnnualRainfallChartFile(session); final Result result = callAction( controllers.routes.ref.Chart.chart("annual_rainfall", "csv", f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/csv"); assertThat(charset(result)).isEqualTo("utf-8"); final ICsvListReader listReader = new CsvListReader( new StringReader(contentAsString(result)), CsvPreference.STANDARD_PREFERENCE); int rowCount = 0; List<String> row = listReader.read(); assertThat(row).contains("Year", "Rainfall (mm)"); while ((row = listReader.read()) != null) { assertThat(row).hasSize(2); // Rainfall years start at 1988 assertThat(row.get(0)).isEqualTo((1988+rowCount)+""); // Test rainfall data starts at 105, increasing by one each year assertThat(row.get(1)).isEqualTo((105+rowCount)+".0"); rowCount++; } assertThat(rowCount).isEqualTo(24); listReader.close(); } // Progress { final String[] condStr = new String[] { null, "Very good", "Good", "Moderate", "Poor", "Very poor" }; final int[][] expected = new int[][] { new int[] { 3, 2, 3, 1, 3, 1, 2, 2, 2 }, new int[] { 1, 0, 2, 0, 4, 2, 0, 0, 5 }, new int[] { 3, 2, 3, 1, 4, 2, 3, 0, 4 }, new int[] { 2, 3, 3, 1, 4, 1, 2, 0, 2 }, new int[] { 1, 2, 1, 1, 2, 5, 1, 0, 2 }, new int[] { 3, 3, 4, 1, 4, 2, 3, 0, 1 }, new int[] { 3, 1, 3, 1, 3, 2, 5, 0, 1 } }; final FileStore.File f = createProgressTableChartFile(session); final Result result = callAction( controllers.routes.ref.Chart.chart("progress_table", "csv", f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(200); assertThat(contentType(result)).isEqualTo("text/csv"); assertThat(charset(result)).isEqualTo("utf-8"); final String content = contentAsString(result); final ICsvListReader listReader = new CsvListReader( new StringReader(content), CsvPreference.STANDARD_PREFERENCE); int rowCount = 0; List<String> row = listReader.read(); { final String[] headers = new String[] { "", "Grazing", "Sugarcane", "Horticulture", "Groundcover", "Nitrogen", "Sediment", "Pesticides", "Dairy", "Phosphorus"}; for (int i = 1; i < row.size(); i++) { assertThat(row.get(i)).isEqualTo(headers[i]); } } while ((row = listReader.read()) != null) { for (int i = 1; i < row.size(); i++) { if (row.get(i) != null) { assertThat(row.get(i)).contains("%"); } final String condition = condStr[expected[rowCount][i-1]]; if (condition == null) { assertThat(row.get(i)).isNull(); } else { assertThat(row.get(i)).startsWith(condition); } } rowCount++; } assertThat(rowCount).isEqualTo(7); listReader.close(); } return session; } }); } @Test public void noChart() { asAdminUser(new F.Function3<Session, User, FakeRequest, Session>() { @Override public Session apply( final Session session, final User user, final FakeRequest newRequest) throws Throwable { final FileStore.File f = createMarineChartFile(session); // Invalid type { final Result result = callAction( controllers.routes.ref.Chart.chart("foobar", "svg", f.getIdentifier()), newRequest); assertThat(status(result)).isEqualTo(404); } // Missing path { final Result result = callAction( controllers.routes.ref.Chart.chart("marine", "svg", UUID.randomUUID().toString()), newRequest); assertThat(status(result)).isEqualTo(404); } return session; } }); } public FileStore.File createMarineChartFile(final Session session) throws RepositoryException, FileNotFoundException { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); return folder.createFile("marine.xlsx", XLSX_MIME_TYPE, new FileInputStream("test/marine.xlsx")); } // Old-style office document private FileStore.File createMarineXlsChartFile(final Session session) throws RepositoryException, FileNotFoundException { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); return folder.createFile("marine.xls", XLS_MIME_TYPE, new FileInputStream("test/marine.xls")); } private FileStore.File createCOTOutbreakChartFile(final Session session) throws RepositoryException, FileNotFoundException { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); return folder.createFile("cots_outbreak.xlsx", XLSX_MIME_TYPE, new FileInputStream("test/cots_outbreak.xlsx")); } private FileStore.File createAnnualRainfallChartFile(final Session session) throws RepositoryException, FileNotFoundException { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); return folder.createFile("annual_rainfall.xlsx", XLSX_MIME_TYPE, new FileInputStream("test/annual_rainfall.xlsx")); } private FileStore.File createProgressTableChartFile(final Session session) throws RepositoryException, FileNotFoundException { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); return folder.createFile("progress_table.xlsx", XLSX_MIME_TYPE, new FileInputStream("test/progress_table.xlsx")); } private FileStore.File createGrazingPracticeChartFile(final Session session) throws RepositoryException, FileNotFoundException { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); return folder.createFile("grazing_practice_systems.xlsx", XLSX_MIME_TYPE, new FileInputStream("test/management_practice_systems.xlsx")); } private FileStore.File createLandPracticeChartFile(final Session session, final String prefix) throws RepositoryException, FileNotFoundException { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); return folder.createFile(prefix + ".xlsx", XLSX_MIME_TYPE, new FileInputStream("test/management_practice_systems.xlsx")); } private FileStore.File createTrackingTowardsTargetsChartFile( final Session session, final String prefix) throws RepositoryException, FileNotFoundException { final FileStore.Folder folder = fileStore().getManager(session).getRoot(); return folder.createFile(prefix + ".xlsx", XLSX_MIME_TYPE, new FileInputStream("test/tracking_towards_targets.xlsx")); } }