// Copyright © 2015 HSL <https://www.hsl.fi>
// This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses.
package fi.hsl.parkandride.itest;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.response.Response;
import fi.hsl.parkandride.back.RequestLogDao;
import fi.hsl.parkandride.core.domain.RequestLogEntry;
import fi.hsl.parkandride.core.service.BatchingRequestLogService;
import fi.hsl.parkandride.core.service.reporting.ReportParameters;
import fi.hsl.parkandride.core.service.reporting.RequestLogInterval;
import fi.hsl.parkandride.front.UrlSchema;
import junit.framework.AssertionFailedError;
import org.apache.poi.ss.usermodel.Sheet;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import javax.inject.Inject;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import static com.jayway.restassured.RestAssured.given;
import static com.jayway.restassured.RestAssured.when;
import static fi.hsl.parkandride.front.ReportController.MEDIA_TYPE_EXCEL;
import static fi.hsl.parkandride.front.RequestLoggingInterceptor.SOURCE_HEADER;
import static fi.hsl.parkandride.front.UrlSchema.FACILITY;
import static fi.hsl.parkandride.front.UrlSchema.HUB;
import static fi.hsl.parkandride.test.DateTimeTestUtils.withDate;
import static java.util.Arrays.asList;
import static java.util.stream.IntStream.range;
import static org.assertj.core.api.Assertions.assertThat;
public class RequestLogITest extends AbstractReportingITest {
private static final String WEB_UI_SOURCE = "liipi-ui";
private static final String REQUEST_LOG = "RequestLog";
@Inject BatchingRequestLogService batchingRequestLogService;
@Inject RequestLogDao requestLogDao;
private String unknownSource;
@Before
public void init() {
unknownSource = messageSource.getMessage("reports.requestlog.unknownSource", null, new Locale("fi"));
}
// ---------------------
// REQUEST LOG REPORT
// ---------------------
@Test
public void report_RequestLog_emptyReport() {
generateDummyRequestLog();
// Defaults to DAY interval, the month is empty so report should be empty
final ReportParameters params = baseParams(BASE_DATE_TIME.minusMonths(2).toLocalDate());
final Response whenPostingToReportUrl = postToReportUrl(params, REQUEST_LOG, adminUser);
// If this succeeds, the response was a valid excel file
withWorkbook(whenPostingToReportUrl, workbook -> {
assertThat(workbook.getSheetName(0)).isEqualTo("Rajapintakutsut");
assertThat(workbook.getSheetName(1)).isEqualTo("Selite");
final Sheet sheet = workbook.getSheetAt(0);
assertThat(getDataFromRow(sheet, 0))
.containsExactly("Päivämäärä", "Lähde", "Polku", "Kutsujen määrä");
assertThat(sheet.getPhysicalNumberOfRows()).isEqualTo(1);
});
}
@Test
public void report_RequestLog_byHour() {
generateDummyRequestLog();
final ReportParameters params = baseParams(BASE_DATE_TIME.toLocalDate());
params.requestLogInterval = RequestLogInterval.HOUR;
final Response whenPostingToReportUrl = postToReportUrl(params, REQUEST_LOG, adminUser);
checkSheetContents(whenPostingToReportUrl, 0,
headerWithTime("Aika"),
asList(ROUNDED_BASE_DATETIME.toString(DATETIME_FORMAT), unknownSource, FACILITY, "12"),
asList(ROUNDED_BASE_DATETIME.toString(DATETIME_FORMAT), unknownSource, HUB, "8"),
asList(ROUNDED_BASE_DATETIME.toString(DATETIME_FORMAT), WEB_UI_SOURCE, FACILITY, "12"),
asList(ROUNDED_BASE_DATETIME.toString(DATETIME_FORMAT), WEB_UI_SOURCE, HUB, "8"),
asList(ROUNDED_BASE_DATETIME.plusHours(1).toString(DATETIME_FORMAT), WEB_UI_SOURCE, FACILITY, "12"),
asList(ROUNDED_BASE_DATETIME.plusDays(1).toString(DATETIME_FORMAT), WEB_UI_SOURCE, FACILITY, "12")
);
}
@Test
public void report_RequestLog_byDay() {
generateDummyRequestLog();
final ReportParameters params = baseParams(BASE_DATE_TIME.toLocalDate());
params.requestLogInterval = RequestLogInterval.DAY;
final Response whenPostingToReportUrl = postToReportUrl(params, REQUEST_LOG, adminUser);
checkSheetContents(whenPostingToReportUrl, 0,
headerWithTime("Päivämäärä"),
asList(ROUNDED_BASE_DATETIME.toString(DATE_FORMAT), unknownSource, FACILITY, "12"),
asList(ROUNDED_BASE_DATETIME.toString(DATE_FORMAT), unknownSource, HUB, "8"),
asList(ROUNDED_BASE_DATETIME.toString(DATE_FORMAT), WEB_UI_SOURCE, FACILITY, "24"),
asList(ROUNDED_BASE_DATETIME.toString(DATE_FORMAT), WEB_UI_SOURCE, HUB, "8"),
asList(ROUNDED_BASE_DATETIME.plusDays(1).toString(DATE_FORMAT), WEB_UI_SOURCE, FACILITY, "12")
);
}
@Test
public void report_RequestLog_byMonth() {
generateDummyRequestLog();
final ReportParameters params = baseParams(BASE_DATE_TIME.toLocalDate());
params.requestLogInterval = RequestLogInterval.MONTH;
params.startDate = BASE_DATE_TIME.minusMonths(1).withDayOfMonth(1).toLocalDate();
final Response whenPostingToReportUrl = postToReportUrl(params, REQUEST_LOG, adminUser);
checkSheetContents(whenPostingToReportUrl, 0,
headerWithTime("Kuukausi"),
asList(ROUNDED_BASE_DATETIME.minusMonths(1).toString(MONTH_FORMAT), WEB_UI_SOURCE, FACILITY, "12"),
asList(ROUNDED_BASE_DATETIME.toString(MONTH_FORMAT), unknownSource, FACILITY, "12"),
asList(ROUNDED_BASE_DATETIME.toString(MONTH_FORMAT), unknownSource, HUB, "8"),
asList(ROUNDED_BASE_DATETIME.toString(MONTH_FORMAT), WEB_UI_SOURCE, FACILITY, "36"),
asList(ROUNDED_BASE_DATETIME.toString(MONTH_FORMAT), WEB_UI_SOURCE, HUB, "8")
);
}
@Test
public void report_RequestLog_emptyParams() {
final Response requestLog = whenPostingToReportUrl(new ReportParameters(), REQUEST_LOG, adminUser);
requestLog.then().assertThat().statusCode(HttpStatus.BAD_REQUEST.value());
}
@Test
public void report_RequestLog_nonExistentUrl_andUrlOutsideApi() {
withDate(BASE_DATE_TIME, () -> {
when().get(UrlSchema.API + "/foobarbazqux");
when().get(UrlSchema.DOCS);
});
batchingRequestLogService.updateRequestLogs();
// Defaults to DAY interval, the month is empty so report should be empty
final ReportParameters params = baseParams(BASE_DATE_TIME.toLocalDate());
final Response whenPostingToReportUrl = postToReportUrl(params, REQUEST_LOG, adminUser);
// If this succeeds, the response was a valid excel file
withWorkbook(whenPostingToReportUrl, workbook -> {
// No requests logged
final Sheet sheet = workbook.getSheetAt(0);
assertThat(sheet.getPhysicalNumberOfRows()).isEqualTo(1);
});
}
@Test
public void report_RequestLog_unauthorized() {
given().contentType(ContentType.JSON)
.accept(MEDIA_TYPE_EXCEL)
.header(authorization(devHelper.login(apiUser.username).token))
.body(new ReportParameters())
.when()
.post(UrlSchema.REPORT, REQUEST_LOG)
.then()
.assertThat().statusCode(HttpStatus.FORBIDDEN.value());
}
@Test
public void illegalApplicationId_resultsInBadRequest() {
given().header(SOURCE_HEADER, "ömmöm").when().get(UrlSchema.FACILITY, 1)
.then().assertThat().statusCode(HttpStatus.BAD_REQUEST.value());
}
@Test
public void generateConcurrent() {
concurrentlyGenerateLogs(1000, 10);
batchingRequestLogService.updateRequestLogs();
final List<RequestLogEntry> logEntriesBetween = requestLogDao.getLogEntriesBetween(DateTime.now().millisOfDay().withMinimumValue(), DateTime.now().millisOfDay().withMaximumValue());
assertThat(logEntriesBetween).hasSize(1)
.containsExactly(new RequestLogEntry(UrlSchema.CAPACITY_TYPES, WEB_UI_SOURCE, DateTime.now().withTime(12, 0, 0, 0), 1000l));
}
private void generateDummyRequestLog() {
// Today
withDate(BASE_DATE_TIME, () -> {
range(0, 12).forEach(i -> given().header(SOURCE_HEADER, WEB_UI_SOURCE).when().get(UrlSchema.FACILITY, facility1.id));
range(0, 8).forEach(i -> given().header(SOURCE_HEADER, WEB_UI_SOURCE).when().get(UrlSchema.HUB, hub.id));
// Without Source header
range(0, 12).forEach(i -> when().get(UrlSchema.FACILITY, facility1.id));
range(0, 8).forEach(i -> when().get(UrlSchema.HUB, hub.id));
});
// An hour after now
withDate(BASE_DATE_TIME.plusHours(1), () -> {
range(0, 12).forEach(i -> given().header(SOURCE_HEADER, WEB_UI_SOURCE).when().get(UrlSchema.FACILITY, facility1.id));
});
// A day after now
withDate(BASE_DATE_TIME.plusDays(1), () -> {
range(0, 12).forEach(i -> given().header(SOURCE_HEADER, WEB_UI_SOURCE).when().get(UrlSchema.FACILITY, facility1.id));
});
// A month before now
withDate(BASE_DATE_TIME.minusMonths(1), () -> {
range(0, 12).forEach(i -> given().header(SOURCE_HEADER, WEB_UI_SOURCE).when().get(UrlSchema.FACILITY, facility1.id));
});
// Store the batch to database
batchingRequestLogService.updateRequestLogs();
}
private void concurrentlyGenerateLogs(int numberOfRequests, int numberOfUpdates) {
withDate(DateTime.now().withTime(12, 2, 0, 0), () -> {
final Stream<CompletableFuture<Integer>> statusCodes = range(0, numberOfRequests).parallel().mapToObj(i -> {
final Response response = given().header(SOURCE_HEADER, WEB_UI_SOURCE).when().get(UrlSchema.CAPACITY_TYPES)
.thenReturn();
return CompletableFuture.completedFuture(response.statusCode());
});
final Stream<CompletableFuture<Integer>> updates = range(0, numberOfUpdates).parallel().mapToObj(i -> {
batchingRequestLogService.updateRequestLogs();
return CompletableFuture.completedFuture(0);
});
try {
CompletableFuture.allOf(Stream.concat(statusCodes, updates).toArray(i -> new CompletableFuture[i])).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
throw new AssertionFailedError(e.getMessage());
}
});
}
private List<String> headerWithTime(String timeColumnHeader) {
return asList(timeColumnHeader, "Lähde", "Polku", "Kutsujen määrä");
}
}