Commit e8ab09ef authored by Jujube Orange's avatar Jujube Orange
Browse files

refactor(integration-tests): less responsibilities on Visitor

parent 1726211e
......@@ -6,25 +6,25 @@ 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;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
@Slf4j
@Component
@ScenarioScope
public class ScenarioContext {
private final Map<String, Visitor> visitors = new HashMap<>(10);
private final Map<String, Visitor> visitors = new HashMap<>();
private final Map<String, LocationQrCodeGenerator> locations = new HashMap<>(10);
private final Map<String, LocationQrCodeGenerator> locations = new HashMap<>();
private final Map<String, LocationQrCodeGenerator> staffLocations = new HashMap<>(10);
private final Map<String, LocationQrCodeGenerator> staffLocations = new HashMap<>();
private final ApplicationProperties applicationProperties;
......@@ -41,7 +41,7 @@ public class ScenarioContext {
}
private Visitor createVisitor(final String name) {
return new Visitor(name, clusterExpositionService, applicationProperties);
return new Visitor(name, clusterExpositionService);
}
public Visitor getVisitor(final String visitorName) {
......
......@@ -40,7 +40,7 @@ public class CleaReportSteps {
final var visitor = this.scenarioContext.getVisitor(visitorName);
final var request = WreportRequest.builder()
.pivotDate(TimeUtils.ntpTimestampFromInstant(Instant.now().minus(Duration.ofDays(14))))
.visits(visitor.getLocalList())
.visits(visitor.getLocalVisitsList())
.build();
final var response = given()
......@@ -61,7 +61,7 @@ public class CleaReportSteps {
final var visitor = scenarioContext.getVisitor(visitorName);
final var request = WreportRequest.builder()
.pivotDate(TimeUtils.ntpTimestampFromInstant(pivotDate))
.visits(visitor.getLocalList())
.visits(visitor.getLocalVisitsList())
.build();
final var response = given()
......@@ -79,7 +79,7 @@ public class CleaReportSteps {
@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 localList = this.scenarioContext.getVisitor(visitorName).getLocalVisitsList();
final var request = new WreportRequest(
TimeUtils.ntpTimestampFromInstant(pivotDate), localList.stream()
.map(visit -> visit.withDeepLinkLocationSpecificPart(""))
......@@ -100,7 +100,7 @@ public class CleaReportSteps {
@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();
final var localList = this.scenarioContext.getVisitor(visitorName).getLocalVisitsList();
final var request = new WreportRequest(
TimeUtils.ntpTimestampFromInstant(pivotDate),
localList.stream()
......@@ -124,7 +124,7 @@ public class CleaReportSteps {
final var visitor = scenarioContext.getVisitor(visitorName);
final var request = new WreportRequest(
TimeUtils.ntpTimestampFromInstant(pivotDate),
visitor.getLocalList().stream()
visitor.getLocalVisitsList().stream()
.map(visit -> visit.withScanTime(-1L))
.collect(Collectors.toList())
);
......@@ -145,7 +145,7 @@ public class CleaReportSteps {
@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();
final var localList = this.scenarioContext.getVisitor(visitorName).getLocalVisitsList();
final var request = new WreportRequest(
TimeUtils.ntpTimestampFromInstant(pivotDate), localList.stream()
.map(visit -> visit.withScanTime(null))
......@@ -192,7 +192,7 @@ public class CleaReportSteps {
@When("{word} declares himself/herself sick with malformed pivot date")
public void visitor_declares_sick_with_malformed_pivotDate(String visitorName) {
final var localList = this.scenarioContext.getVisitor(visitorName).getLocalList();
final var localList = this.scenarioContext.getVisitor(visitorName).getLocalVisitsList();
given()
.contentType(ContentType.JSON)
......
......@@ -43,6 +43,6 @@ public class ClusterDetectionSteps {
public void visitor_should_include_only_expected_visits(String visitorName, Integer nbVisits, String locationName,
String qrScanTime) {
final var visitor = this.scenarioContext.getVisitor(visitorName);
assertThat(visitor.getLocalList().size()).isEqualTo(nbVisits);
assertThat(visitor.getLocalVisitsList().size()).isEqualTo(nbVisits);
}
}
package fr.gouv.clea.integrationtests.repository;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.gouv.clea.integrationtests.config.ApplicationProperties;
import fr.gouv.clea.integrationtests.repository.model.Cluster;
import fr.gouv.clea.integrationtests.repository.model.ClusterIndex;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class ClusterExpositionRepository {
private final ObjectMapper objectMapper;
private final MinioClient minioClient;
private final ApplicationProperties applicationProperties;
public ClusterIndex getClusterIndex() {
return getJsonFile("v1/clusterIndex.json", ClusterIndex.class);
}
public List<Cluster> getClusterFile(int iteration, String prefix) {
final var filePath = String.format("v1/%d/%s.json", iteration, prefix);
final var clusters = getJsonFile(filePath, Cluster[].class);
return List.of(clusters);
}
private <T> T getJsonFile(String key, Class<T> valueType) {
final var content = getFile(key);
try {
return objectMapper.readValue(content, valueType);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private byte[] getFile(String key) {
final var args = GetObjectArgs.builder()
.bucket(applicationProperties.getBucket().getBucketName())
.object(key)
.build();
try (final var minioObjectStream = minioClient.getObject(args)) {
return IOUtils.toByteArray(minioObjectStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
......@@ -2,6 +2,8 @@ package fr.gouv.clea.integrationtests.repository;
import fr.gouv.clea.integrationtests.repository.model.LocationStat;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface LocationStatRepository extends ElasticsearchRepository<LocationStat, String> {
}
......@@ -2,6 +2,8 @@ package fr.gouv.clea.integrationtests.repository;
import fr.gouv.clea.integrationtests.repository.model.ReportStat;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ReportStatRepository extends ElasticsearchRepository<ReportStat, String> {
}
package fr.gouv.clea.integrationtests.service.model;
package fr.gouv.clea.integrationtests.repository.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
......
package fr.gouv.clea.integrationtests.service.model;
package fr.gouv.clea.integrationtests.repository.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import fr.inria.clea.lsp.utils.TimeUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Duration;
import java.time.Instant;
import static fr.inria.clea.lsp.utils.TimeUtils.SECONDS_FROM_01_01_1900_TO_01_01_1970;
@Data
@AllArgsConstructor
@NoArgsConstructor
......@@ -24,9 +22,11 @@ public class ClusterExposition {
@JsonProperty("r")
private float risk;
public boolean isInExposition(final Instant instant) {
Instant startTime = Instant.ofEpochSecond(startTimeAsNtpTimestamp - SECONDS_FROM_01_01_1900_TO_01_01_1970);
long delta = Duration.between(startTime, instant).toSeconds();
return (delta >= 0 && delta <= durationInSeconds);
public boolean affects(final Instant instant) {
final var clusterStartTime = TimeUtils.instantFromTimestamp(startTimeAsNtpTimestamp);
final var clusterEndTime = clusterStartTime.plusSeconds(durationInSeconds);
return instant.equals(clusterStartTime)
|| instant.isAfter(clusterStartTime) && instant.isBefore(clusterEndTime)
|| instant.equals(clusterEndTime);
}
}
package fr.gouv.clea.integrationtests.service.model;
package fr.gouv.clea.integrationtests.repository.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
......
package fr.gouv.clea.integrationtests.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.gouv.clea.integrationtests.config.ApplicationProperties;
import fr.gouv.clea.integrationtests.service.model.Cluster;
import fr.gouv.clea.integrationtests.service.model.ClusterIndex;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import fr.gouv.clea.integrationtests.repository.ClusterExpositionRepository;
import fr.gouv.clea.integrationtests.repository.model.Cluster;
import fr.gouv.clea.integrationtests.repository.model.ClusterExposition;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class ClusterExpositionService {
private final ObjectMapper objectMapper;
private final MinioClient minioClient;
private final ApplicationProperties applicationProperties;
public ClusterIndex getClusterIndex() {
return getJsonFile("v1/clusterIndex.json", ClusterIndex.class);
}
public List<Cluster> getClusterFile(int iteration, String prefix) {
final var filePath = String.format("v1/%d/%s.json", iteration, prefix);
final var clusters = getJsonFile(filePath, Cluster[].class);
return List.of(clusters);
}
private <T> T getJsonFile(String key, Class<T> valueType) {
final var content = getFile(key);
try {
return objectMapper.readValue(content, valueType);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private byte[] getFile(String key) {
final var args = GetObjectArgs.builder()
.bucket(applicationProperties.getBucket().getBucketName())
.object(key)
.build();
try (final var minioObjectStream = minioClient.getObject(args)) {
return IOUtils.toByteArray(minioObjectStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
private final ClusterExpositionRepository clusterExpositionRepository;
public float getRiskLevelForPlaceAtInstant(String locationTemporaryPublicId, Instant instant) {
final var clusterIndex = clusterExpositionRepository.getClusterIndex();
return clusterIndex.getPrefixes()
.stream()
.filter(locationTemporaryPublicId::startsWith)
.map(prefix -> clusterExpositionRepository.getClusterFile(clusterIndex.getIteration(), prefix))
.flatMap(List::stream)
.filter(cluster -> cluster.getLocationTemporaryPublicID().equals(locationTemporaryPublicId))
.map(Cluster::getExpositions)
.flatMap(List::stream)
.filter(clusterExposition -> clusterExposition.affects(instant))
.map(ClusterExposition::getRisk)
.sorted()
.findFirst()
.orElse(0f);
}
}
package fr.gouv.clea.integrationtests.service.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ClusterFile {
private List<Cluster> clusters;
}
package fr.gouv.clea.integrationtests.service.visitorsimulator;
import fr.gouv.clea.integrationtests.config.ApplicationProperties;
import fr.gouv.clea.integrationtests.service.ClusterExpositionService;
import fr.gouv.clea.integrationtests.service.model.Cluster;
import fr.gouv.clea.integrationtests.service.model.ClusterExposition;
import fr.gouv.clea.integrationtests.utils.QrCodeDecoder;
import fr.inria.clea.lsp.utils.TimeUtils;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.Setter;
import java.time.Instant;
import java.util.*;
import java.util.stream.Stream;
@Data
@Slf4j
@RequiredArgsConstructor
public class Visitor {
private static final String DEEPLINK_COUNTRY_PART = "https://tac.gouv.fr?v=0#";
@Getter
private final String name;
private final ClusterExpositionService s3Service;
private final ClusterExpositionService clusterExpositionService;
private final ApplicationProperties applicationProperties;
private final List<Visit> localVisitsList = new ArrayList<>();
private List<Visit> localList = new ArrayList<>();
@Getter(AccessLevel.NONE)
@Setter
private WreportResponse lastReportResponse = null;
public float getStatus() {
final var clusterIndex = s3Service.getClusterIndex();
return clusterIndex.getPrefixes().stream()
.filter(this::matchesVisitedPlacesIds)
.map(prefix -> getRiskLevelsFromQrCodesMatchingPrefix(clusterIndex.getIteration(), prefix))
.flatMap(Stream::distinct)
.flatMap(Stream::distinct)
.filter(Optional::isPresent)
.map(Optional::get)
.max(Comparator.naturalOrder()).orElse(0f);
public List<Visit> getLocalVisitsList() {
return Collections.unmodifiableList(localVisitsList);
}
private Stream<Stream<Optional<Float>>> getRiskLevelsFromQrCodesMatchingPrefix(final int iteration,
final String prefix) {
return s3Service.getClusterFile(iteration, prefix).stream()
.map(
cluster -> localList.stream()
.map(qr -> getQrcodeRiskLevel(cluster, qr))
);
public float getStatus() {
return localVisitsList.stream()
.map(this::getRiskLevelForVisit)
.max(Comparator.naturalOrder())
.orElse(0f);
}
private Optional<Float> getQrcodeRiskLevel(final Cluster cluster, final Visit visit) {
UUID locationTemporaryId = QrCodeDecoder.getLocationTemporaryId(visit);
if (locationTemporaryId.toString().equals(cluster.getLocationTemporaryPublicID())) {
return cluster.getExpositions().stream()
.filter(exp -> exp.isInExposition(TimeUtils.instantFromTimestamp(visit.getScanTime())))
.map(ClusterExposition::getRisk)
.max(Float::compare);
}
return Optional.empty();
private float getRiskLevelForVisit(Visit visit) {
final var ltid = QrCodeDecoder.getLocationTemporaryPublicId(visit);
final var visitTime = TimeUtils.instantFromTimestamp(visit.getScanTime());
return clusterExpositionService.getRiskLevelForPlaceAtInstant(ltid.toString(), visitTime);
}
public Optional<WreportResponse> getLastReportResponse() {
......@@ -78,17 +54,10 @@ public class Visitor {
}
final var encodedInformation = deepLink.substring(DEEPLINK_COUNTRY_PART.length());
localList.add(
localVisitsList.add(
Visit.builder()
.deepLinkLocationSpecificPart(encodedInformation)
.scanTime(TimeUtils.ntpTimestampFromInstant(scanTime)).build()
);
}
private boolean matchesVisitedPlacesIds(final String prefix) {
return localList.stream()
.map(QrCodeDecoder::getLocationTemporaryId)
.map(UUID::toString)
.anyMatch(qrId -> qrId.startsWith(prefix));
}
}
......@@ -11,7 +11,7 @@ import java.util.UUID;
@UtilityClass
public class QrCodeDecoder {
public static UUID getLocationTemporaryId(final Visit visit) {
public static UUID getLocationTemporaryPublicId(final Visit visit) {
try {
return new LocationSpecificPartDecoder()
.decodeHeader(Base64.getUrlDecoder().decode(visit.getDeepLinkLocationSpecificPart()))
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment