Commit 1726211e authored by Jujube Orange's avatar Jujube Orange
Browse files

Merge branch 'feature/integration-tests-packages-layout' into 'develop'

integration tests packages layout

See merge request !33
parents 67c66a76 8f268be5
Pipeline #296069 failed with stages
in 8 minutes and 51 seconds
......@@ -44,5 +44,5 @@ integration-tests:
- docker-compose down
artifacts:
paths:
- "$CI_PROJECT_DIR/cucumber-reports.html"
- "$CI_PROJECT_DIR/target/cucumber-reports.html"
expire_in: 2 weeks
......@@ -5,6 +5,6 @@ import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(features = { "classpath:features" }, plugin = { "pretty", "html:cucumber-reports.html" })
@CucumberOptions(features = { "classpath:features" }, plugin = { "pretty", "html:target/cucumber-reports.html" })
public class CucumberTest {
}
package fr.gouv.clea.integrationtests.feature;
package fr.gouv.clea.integrationtests.cucumber;
import io.cucumber.java.Before;
import lombok.RequiredArgsConstructor;
......
package fr.gouv.clea.integrationtests.feature;
package fr.gouv.clea.integrationtests.cucumber;
import fr.gouv.clea.integrationtests.model.LocationStat;
import fr.gouv.clea.integrationtests.model.ReportStat;
import fr.gouv.clea.integrationtests.repository.model.LocationStat;
import fr.gouv.clea.integrationtests.repository.model.ReportStat;
import io.cucumber.java.DataTableType;
import org.ocpsoft.prettytime.nlp.PrettyTimeParser;
......
package fr.gouv.clea.integrationtests.cucumber;
import io.cucumber.java.ParameterType;
import org.ocpsoft.prettytime.nlp.PrettyTimeParser;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static java.lang.String.format;
public class ParameterTypes {
@ParameterType(".*")
public Instant naturalTime(final String timeExpression) {
final var dates = new PrettyTimeParser()
.parse(timeExpression);
if (dates.size() != 1) {
final var message = format(
"Expecting to find exactly 1 date expression but found %d in '%s'",
dates.size(), timeExpression
);
throw new IllegalArgumentException(message);
}
return dates.stream()
.findAny()
.orElseThrow()
.toInstant();
}
@ParameterType("(\\d+) (days|hours|minutes)")
public Duration duration(final String amountExpression, final String unitExpression) {
final var amount = Integer.parseInt(amountExpression);
final var unit = ChronoUnit.valueOf(unitExpression.toUpperCase());
return Duration.of(amount, unit);
}
}
package fr.gouv.clea.integrationtests.feature.context;
package fr.gouv.clea.integrationtests.cucumber;
import fr.gouv.clea.integrationtests.config.ApplicationProperties;
import fr.gouv.clea.integrationtests.service.CleaS3Service;
import fr.gouv.clea.integrationtests.service.ClusterExpositionService;
import fr.gouv.clea.integrationtests.service.visitorsimulator.Visitor;
import fr.gouv.clea.qr.LocationQrCodeGenerator;
import fr.inria.clea.lsp.exception.CleaCryptoException;
import io.cucumber.spring.ScenarioScope;
......@@ -14,9 +15,6 @@ import java.time.Instant;
import java.util.*;
import java.util.function.Predicate;
import static fr.gouv.clea.integrationtests.feature.context.VenueConfiguration.DEFAULT;
import static java.util.Collections.singleton;
@Slf4j
@Component
@ScenarioScope
......@@ -24,20 +22,18 @@ public class ScenarioContext {
private final Map<String, Visitor> visitors = new HashMap<>(10);
private final Set<VenueConfiguration> venueConfigurations = new HashSet<>(singleton(DEFAULT));
private final Map<String, LocationQrCodeGenerator> locations = new HashMap<>(10);
private final Map<String, LocationQrCodeGenerator> staffLocations = new HashMap<>(10);
private final ApplicationProperties applicationProperties;
private final CleaS3Service s3service;
private final ClusterExpositionService clusterExpositionService;
public ScenarioContext(final ApplicationProperties applicationProperties, final CleaS3Service s3service)
throws Exception {
public ScenarioContext(final ApplicationProperties applicationProperties,
final ClusterExpositionService clusterExpositionService) {
this.applicationProperties = applicationProperties;
this.s3service = s3service;
this.clusterExpositionService = clusterExpositionService;
}
public Visitor getOrCreateUser(final String name) {
......@@ -45,19 +41,13 @@ public class ScenarioContext {
}
private Visitor createVisitor(final String name) {
return new Visitor(name, s3service, applicationProperties);
return new Visitor(name, clusterExpositionService, applicationProperties);
}
public Visitor getVisitor(final String visitorName) {
return visitors.get(visitorName);
}
// TODO remonter le venueType au niveau maximum
public void updateOrCreateRiskConfig(final Integer venueType, final Integer venueCategory1,
final Integer venueCategory2) {
venueConfigurations.add(new VenueConfiguration(venueType, venueCategory1, venueCategory2));
}
private LocationQrCodeGenerator createDynamicLocation(final String locationName, final Instant periodStartTime,
final Integer venueType, final Integer venueCategory1, final Integer venueCategory2,
final Duration qrCodeRenewalInterval, final Integer periodDuration) throws CleaCryptoException {
......@@ -84,13 +74,12 @@ public class ScenarioContext {
final Integer qrCodeRenewalIntervalExponentCompact, final Integer periodDuration)
throws CleaCryptoException {
final var permanentLocationSecretKey = Hex.toHexString(UUID.randomUUID().toString().getBytes());
final VenueConfiguration venueConfiguration = findOrCreate(venueType, venueCategory1, venueCategory2);
final var location = LocationQrCodeGenerator.builder()
.countryCode(250) // France Country Code
.staff(false)
.venueType(venueConfiguration.getVenueType())
.venueCategory1(venueConfiguration.getVenueCategory1())
.venueCategory2(venueConfiguration.getVenueCategory2())
.venueType(venueType)
.venueCategory1(venueCategory1)
.venueCategory2(venueCategory2)
.periodDuration(periodDuration)
.periodStartTime(periodStartTime)
.qrCodeRenewalIntervalExponentCompact(qrCodeRenewalIntervalExponentCompact)
......@@ -103,9 +92,9 @@ public class ScenarioContext {
final var staffLocation = LocationQrCodeGenerator.builder()
.countryCode(250) // France Country Code
.staff(true)
.venueType(venueConfiguration.getVenueType())
.venueCategory1(venueConfiguration.getVenueCategory1())
.venueCategory2(venueConfiguration.getVenueCategory2())
.venueType(venueType)
.venueCategory1(venueCategory1)
.venueCategory2(venueCategory2)
.periodDuration(periodDuration)
.periodStartTime(periodStartTime)
.qrCodeRenewalIntervalExponentCompact(qrCodeRenewalIntervalExponentCompact)
......@@ -120,19 +109,6 @@ public class ScenarioContext {
return location;
}
private VenueConfiguration findOrCreate(Integer venueType, Integer venueCategory1, Integer venueCategory2) {
return venueConfigurations.stream()
.filter(matchingConfigurationExists(venueType, venueCategory1, venueCategory2))
.findFirst()
.orElseGet(() -> {
VenueConfiguration newConfiguration = new VenueConfiguration(
venueType, venueCategory1, venueCategory2
);
venueConfigurations.add(newConfiguration);
return newConfiguration;
});
}
private Predicate<VenueConfiguration> matchingConfigurationExists(Integer venueType, Integer venueCategory1,
Integer venueCategory2) {
return config -> config.getVenueType() == venueType &&
......
package fr.gouv.clea.integrationtests.feature.context;
package fr.gouv.clea.integrationtests.cucumber;
import lombok.AllArgsConstructor;
import lombok.Value;
......
package fr.gouv.clea.integrationtests.cucumber.steps;
import fr.gouv.clea.integrationtests.cucumber.ScenarioContext;
import fr.inria.clea.lsp.exception.CleaCryptoException;
import io.cucumber.java.en.Given;
import lombok.AllArgsConstructor;
import java.time.Duration;
import java.time.Instant;
@AllArgsConstructor
public class CleaQrCodeSteps {
private final ScenarioContext scenarioContext;
// Dynamic Location
@Given("{string} created a dynamic QRCode at {naturalTime} with VType as {int}, with VCategory1 as {int}, with VCategory2 as {int}, with a renewal time of {duration} and with a periodDuration of {int} hours")
public void dynamic_location_with_a_periodDuration_and_renewalTime(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Duration qrCodeRenewalIntervalDuration,
Integer periodDuration) throws CleaCryptoException {
this.scenarioContext.getOrCreateDynamicLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalIntervalDuration,
periodDuration
);
// TODO: add QR id
}
@Given("{string} created a dynamic QRCode at {naturalTime} with VType as {int} and with VCategory1 as {int} and with VCategory2 as {int} and with and with a renewal time of {duration}")
public void dynamic_location_without_periodDuration_with_renewalTime(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Duration qrCodeRenewalIntervalDuration)
throws CleaCryptoException {
this.scenarioContext.getOrCreateDynamicLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalIntervalDuration
);
// TODO: add QR id
}
@Given("{string} created a static QRCode at {naturalTime} with VType as {int}, with VCategory1 as {int}, with VCategory2 as {int} and with a periodDuration of {int} hours")
public void static_location_without_renewalTime_with_periodDuration(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Integer periodDuration)
throws CleaCryptoException {
this.scenarioContext.getOrCreateStaticLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
periodDuration
);
// TODO: add QR id
}
@Given("{string} created a static QRCode at {naturalTime} with VType as {string} and VCategory1 as {int} and with VCategory2 as {int}")
public void static_location_with_default_periodDuration(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2) throws CleaCryptoException {
this.scenarioContext.getOrCreateStaticLocationWithUnlimitedDuration(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2
);
// TODO: add QR id
}
}
package fr.gouv.clea.integrationtests.feature;
package fr.gouv.clea.integrationtests.cucumber.steps;
import fr.gouv.clea.integrationtests.config.ApplicationProperties;
import fr.gouv.clea.integrationtests.dto.PivotDateStringWreportRequest;
import fr.gouv.clea.integrationtests.dto.WreportRequest;
import fr.gouv.clea.integrationtests.dto.WreportResponse;
import fr.gouv.clea.integrationtests.feature.context.ScenarioContext;
import fr.gouv.clea.integrationtests.model.LocationStat;
import fr.gouv.clea.integrationtests.model.ReportStat;
import fr.gouv.clea.integrationtests.repository.LocationStatRepository;
import fr.gouv.clea.integrationtests.repository.ReportStatRepository;
import fr.gouv.clea.integrationtests.service.CleaBatchService;
import fr.gouv.clea.integrationtests.cucumber.ScenarioContext;
import fr.gouv.clea.integrationtests.service.visitorsimulator.WreportRequest;
import fr.gouv.clea.integrationtests.service.visitorsimulator.WreportResponse;
import fr.gouv.clea.integrationtests.utils.CleaApiResponseParser;
import fr.inria.clea.lsp.exception.CleaCryptoException;
import fr.inria.clea.lsp.utils.TimeUtils;
import io.cucumber.core.exception.CucumberException;
import io.cucumber.java.ParameterType;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import io.minio.errors.*;
import io.restassured.http.ContentType;
import lombok.extern.slf4j.Slf4j;
import org.ocpsoft.prettytime.nlp.PrettyTimeParser;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static io.restassured.RestAssured.given;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.awaitility.pollinterval.FibonacciPollInterval.fibonacci;
import static org.hamcrest.Matchers.equalTo;
@Slf4j
public class CleaClientStepDefinitions {
public class CleaReportSteps {
private final ScenarioContext scenarioContext;
private final CleaBatchService cleaBatchService;
private final URL cleaReportUrl;
private final String wreportUrl;
private final ReportStatRepository reportStatRepository;
private final LocationStatRepository locationStatRepository;
public CleaClientStepDefinitions(final ScenarioContext scenarioContext,
final CleaBatchService cleaBatchService,
final ApplicationProperties applicationProperties, ReportStatRepository reportStatRepository,
LocationStatRepository locationStatRepository) {
public CleaReportSteps(ScenarioContext scenarioContext, ApplicationProperties applicationProperties)
throws MalformedURLException {
this.scenarioContext = scenarioContext;
this.cleaBatchService = cleaBatchService;
this.wreportUrl = applicationProperties.getWsRest().getBaseUrl().toString().concat("/api/clea/v1/wreport");
this.reportStatRepository = reportStatRepository;
this.locationStatRepository = locationStatRepository;
}
// TODO Robert registration of the user -> integration tests perimeters to be
// specified, do we need to test interactions between all apps?
@Given("{string} registered on TAC")
public void registered_on_tac(final String username) {
this.scenarioContext.getOrCreateUser(username);
}
@Given("VType of {int}, VCategory1 of {int} and VCategory2 of {int}")
public void create_or_update_venue_with_specific_configuration(Integer venueType, Integer venueCategory1,
Integer venueCategory2) {
this.scenarioContext.updateOrCreateRiskConfig(venueType, venueCategory1, venueCategory2);
}
// Dynamic Location
@Given("{string} created a dynamic QRCode at {instant} with VType as {int}, with VCategory1 as {int}, with VCategory2 as {int}, with a renewal time of \"{int} {word}\" and with a periodDuration of \"{int} hours\"")
public void dynamic_location_with_a_periodDuration_and_renewalTime(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Integer qrCodeRenewalInterval,
String qrCodeRenewalIntervalUnit, Integer periodDuration) throws CleaCryptoException {
final var qrCodeRenewalIntervalDuration = Duration
.of(qrCodeRenewalInterval, ChronoUnit.valueOf(qrCodeRenewalIntervalUnit.toUpperCase()));
this.scenarioContext.getOrCreateDynamicLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalIntervalDuration,
periodDuration
);
// TODO: add QR id
}
@Given("{string} created a dynamic QRCode at {instant} with VType as {int} and with VCategory1 as {int} and with VCategory2 as {int} and with and with a renewal time of \"{int} {word}\"")
public void dynamic_location_without_periodDuration_with_renewalTime(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Integer qrCodeRenewalInterval,
String qrCodeRenewalIntervalUnit) throws CleaCryptoException {
final var qrCodeRenewalIntervalDuration = Duration
.of(qrCodeRenewalInterval, ChronoUnit.valueOf(qrCodeRenewalIntervalUnit.toUpperCase()));
this.scenarioContext.getOrCreateDynamicLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalIntervalDuration
);
// TODO: add QR id
}
@Given("{string} created a static QRCode at {instant} with VType as {int}, with VCategory1 as {int}, with VCategory2 as {int} and with a periodDuration of \"{int} hours\"")
public void static_location_without_renewalTime_with_periodDuration(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Integer periodDuration)
throws CleaCryptoException {
this.scenarioContext.getOrCreateStaticLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
periodDuration
);
// TODO: add QR id
}
@Given("{string} created a static QRCode at {instant} with VType as {string} and VCategory1 as {int} and with VCategory2 as {int}")
public void static_location_with_default_periodDuration(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2) throws CleaCryptoException {
this.scenarioContext.getOrCreateStaticLocationWithUnlimitedDuration(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2
);
// TODO: add QR id
}
// Visitor scan a QR code at given instant
@Given("{string} recorded a visit to {string} at {instant}")
public void visitor_scans_qrcode_at_given_instant(String visitorName, String locationName, Instant qrCodeScanTime)
throws CleaCryptoException {
final var location = this.scenarioContext.getLocation(locationName);
final var deepLink = location.getQrCodeAt(qrCodeScanTime);
this.scenarioContext.getOrCreateUser(visitorName).registerDeepLink(deepLink.getQrCode(), qrCodeScanTime);
}
// Visitor scan a staff QR code at given instant
@Given("{string} recorded a visit as a STAFF to {string} at {instant}")
public void visitor_scans_staff_qrcode_at_given_instant(String visitorName, String locationName,
Instant qrCodeScanTime) throws CleaCryptoException {
final var location = this.scenarioContext.getStaffLocation(locationName);
final var qr = location.getQrCodeAt(qrCodeScanTime);
this.scenarioContext.getOrCreateUser(visitorName).registerDeepLink(qr.getQrCode(), qrCodeScanTime);
this.cleaReportUrl = new URL(applicationProperties.getWsRest().getBaseUrl(), "/api/clea/v1/wreport");
}
// Visitor scan a QR code at a given Instant, but the scanned QR code is valid
// for another Instant
@Given("{string} recorded a visit to {string} at {instant} with a QR code valid for {string}")
public void visitor_scans_qrcode_at_given_instant_but_qr_code_valid_for_another_instant(String visitorName,
String locationName, Instant qrCodeScanTime, Instant qrCodeValidTime) throws CleaCryptoException {
final var location = this.scenarioContext.getLocation(locationName);
final var qr = location.getQrCodeAt(qrCodeValidTime);
this.scenarioContext.getOrCreateUser(visitorName).registerDeepLink(qr.getQrCode(), qrCodeScanTime);
}
@When("Cluster detection triggered")
public void trigger_cluster_identification() throws IOException, InterruptedException {
cleaBatchService.triggerNewClusterIdenfication();
}
@When("{string} declares himself/herself sick")
@When("{word} declares himself/herself sick")
public void visitor_declares_himself_sick(String visitorName) {
final var visitor = this.scenarioContext.getVisitor(visitorName);
final var request = WreportRequest.builder()
......@@ -171,7 +47,7 @@ public class CleaClientStepDefinitions {
.contentType(ContentType.JSON)
.body(request)
.when()
.post(wreportUrl)
.post(cleaReportUrl)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
......@@ -180,7 +56,7 @@ public class CleaClientStepDefinitions {
visitor.setLastReportResponse(response);
}
@When("{string} declares himself/herself sick with a {instant} pivot date")
@When("{word} declares himself/herself sick with a {naturalTime} pivot date")
public void visitor_declares_sick(String visitorName, Instant pivotDate) {
final var visitor = scenarioContext.getVisitor(visitorName);
final var request = WreportRequest.builder()
......@@ -192,7 +68,7 @@ public class CleaClientStepDefinitions {
.contentType(ContentType.JSON)
.body(request)
.when()
.post(wreportUrl)
.post(cleaReportUrl)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
......@@ -201,7 +77,7 @@ public class CleaClientStepDefinitions {
visitor.setLastReportResponse(response);
}
@When("{string} declares himself/herself sick with a {instant} pivot date with no QRCode")
@When("{word} declares himself/herself sick with a {naturalTime} pivot date with no QRCode")
public void visitor_declares_sick_with_pivot_date_and_no_deeplink(String visitorName, Instant pivotDate) {
final var localList = this.scenarioContext.getVisitor(visitorName).getLocalList();
final var request = new WreportRequest(
......@@ -214,14 +90,14 @@ public class CleaClientStepDefinitions {
.contentType(ContentType.JSON)
.body(request)
.when()
.post(wreportUrl)
.post(cleaReportUrl)
.then()
.contentType(ContentType.JSON)
.statusCode(400)
.body("message", equalTo("Invalid request"));
}
@When("{string} declares himself/herself sick with malformed QrCode")
@When("{word} declares himself/herself sick with malformed QrCode")
public void visitor_declares_sick_with_malformed_deeplink(String visitorName) {
final Instant pivotDate = Instant.now().minus(Duration.ofDays(13));
final var localList = this.scenarioContext.getVisitor(visitorName).getLocalList();
......@@ -235,14 +111,14 @@ public class CleaClientStepDefinitions {
.contentType(ContentType.JSON)
.body(request)
.when()
.post(wreportUrl)
.post(cleaReportUrl)
.then()
.contentType(ContentType.JSON)
.statusCode(500)
.body("message", equalTo("Last unit does not have enough valid bits"));
}
@When("{string} declares himself/herself sick with malformed scan time")
@When("{word} declares himself/herself sick with malformed scan time")
public void visitor_declares_sick_with_malformed_scanTime(String visitorName) {
final Instant pivotDate = Instant.now().minus(Duration.ofDays(14));
final var visitor = scenarioContext.getVisitor(visitorName);
......@@ -256,7 +132,7 @@ public class CleaClientStepDefinitions {
.contentType(ContentType.JSON)
.body(request)
.when()
.post(wreportUrl)
.post(cleaReportUrl)
.then()
.contentType(ContentType.JSON)
.statusCode(200)
......@@ -266,7 +142,7 @@ public class CleaClientStepDefinitions {
visitor.setLastReportResponse(response);
}
@When("{string} declares himself/herself sick with no scan time")
@When("{word} declares himself/herself sick with no scan time")
public void visitor_declares_sick_with_no_scanTime(String visitorName) {
final Instant pivotDate = Instant.now().minus(Duration.ofDays(14));
final var localList = this.scenarioContext.getVisitor(visitorName).getLocalList();
......@@ -279,42 +155,14 @@ public class CleaClientStepDefinitions {
.contentType(ContentType.JSON)
.body(request)
.when()
.post(wreportUrl)
.post(cleaReportUrl)
.then()
.contentType(ContentType.JSON)
.statusCode(400)
.body("message", equalTo("Invalid request"));
}
@When("{string} asks for exposure status")
public void visitor_asks_for_exposure_status(final String visitorName) {
noOp();
}
@Then("Exposure status should reports {string} as not being at risk")
public void visitor_should_not_be_at_risk(String visitorName) throws IOException, ServerException,
InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException,
InvalidResponseException, XmlParserException, InternalException {
final var riskLevel = this.scenarioContext.getOrCreateUser(visitorName).getStatus();
assertThat(riskLevel).isEqualTo(0);
}
@Then("Exposure status should reports {string} as being at risk of {float}")
public void visitor_should_be_at_specified_risk(String visitorName, Float risk) throws IOException, ServerException,
InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException,
InvalidResponseException, XmlParserException, InternalException {