This is an automated email from the ASF dual-hosted git repository. dgriffon pushed a commit to branch UNOMI-825-add-concealed-profile-properties in repository https://gitbox.apache.org/repos/asf/unomi.git
commit 73908fa5943891063471320573017cae5e65dc1f Author: david.griffon <dgrif...@jahia.com> AuthorDate: Tue Apr 16 07:56:19 2024 +0200 UNOMI-825: add concealed profile properties --- itests/README.md | 2 +- .../org/apache/unomi/itests/ContextServletIT.java | 691 +-------------------- .../unomi/rest/endpoints/ContextJsonEndpoint.java | 17 +- 3 files changed, 44 insertions(+), 666 deletions(-) diff --git a/itests/README.md b/itests/README.md index 7be12dcd0..7da3a0e24 100644 --- a/itests/README.md +++ b/itests/README.md @@ -83,7 +83,7 @@ https://maven.apache.org/surefire/maven-failsafe-plugin/examples/single-test.htm Here's an example: mvn clean install -Dit.karaf.debug=hold:true -Dit.test=org.apache.unomi.itests.graphql.GraphQLEventIT - +mvn clean install -Dit.karaf.debug=hold:true,port=5006 -Dit.test=org.apache.unomi.itests.ContextServletIT ## Migration tests Migration can now be tested, by reusing an ElasticSearch snapshot. diff --git a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java index 10ed9e137..d513a296e 100644 --- a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java @@ -139,676 +139,51 @@ public class ContextServletIT extends BaseIT { } @Test - public void testUpdateEventFromContextAuthorizedThirdParty_Success() throws Exception { - //Arrange - String eventId = "test-event-id-" + System.currentTimeMillis(); - String sessionId = "test-session-id"; - String scope = TEST_SCOPE; - String eventTypeOriginal = "test-event-type-original"; - Profile profile = new Profile(TEST_PROFILE_ID); - Session session = new Session(sessionId, profile, new Date(), scope); - Event event = new Event(eventId, eventTypeOriginal, session, profile, scope, null, null, new Date()); + public void testConcealedProperties() throws Exception { + String sessionId = "test-concealed-property-session-id"; + // Add custom profile property type + PropertyType customPropertyType = new PropertyType(new Metadata("customProperty")); + customPropertyType.setValueTypeId("text"); + profileService.setPropertyType(customPropertyType); + // New profile with the custom property type + Profile profile = new Profile("test-profile-id" + System.currentTimeMillis()); + profile.setProperty("customProperty", "concealedValue"); profileService.save(profile); - keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID), - Objects::nonNull, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - - this.eventService.send(event); - - keepTrying("Event " + eventId + " not updated in the required time", () -> this.eventService.getEvent(eventId), - savedEvent -> Objects.nonNull(savedEvent) && eventTypeOriginal.equals(savedEvent.getEventType()), DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - - event.setEventType(TEST_EVENT_TYPE); //change the event so we can see the update effect - - //Act - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setSessionId(session.getItemId()); - contextRequest.setEvents(Arrays.asList(event)); - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY); - request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request, sessionId); - - event = keepTrying("Event " + eventId + " not updated in the required time", () -> eventService.getEvent(eventId), - savedEvent -> Objects.nonNull(savedEvent) && TEST_EVENT_TYPE.equals(savedEvent.getEventType()), DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - assertEquals(2, event.getVersion().longValue()); - } - - @Test - public void testCallingContextWithSessionCreation() throws Exception { - //Arrange - String eventId = "test-event-id-" + System.currentTimeMillis(); - String sessionId = "test-session-id"; - Profile profile = new Profile(TEST_PROFILE_ID); - profileService.save(profile); - - keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID), - Objects::nonNull, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - - //Act - Event event = new Event(TEST_EVENT_TYPE, null, profile, TEST_SCOPE, null, null, new Date()); - + Thread.sleep(2000); + // Get it from all properties ContextRequest contextRequest = new ContextRequest(); + contextRequest.setRequiredProfileProperties(Arrays.asList("*")); + contextRequest.setProfileId(profile.getItemId()); contextRequest.setSessionId(sessionId); - contextRequest.setEvents(Collections.singletonList(event)); HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY); request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request, sessionId); - - Session session = keepTrying("Session with the id " + sessionId + " not saved in the required time", - () -> profileService.loadSession(sessionId), Objects::nonNull, DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - - assertEquals(TEST_EVENT_TYPE, session.getOriginEventTypes().get(0)); - assertFalse(session.getOriginEventIds().isEmpty()); - } - - @Test - public void testUpdateEventFromContextUnAuthorizedThirdParty_Fail() throws Exception { - //Arrange - String eventId = "test-event-id-" + System.currentTimeMillis(); - String sessionId = "test-session-id"; - String scope = TEST_SCOPE; - String eventTypeOriginal = "test-event-type-original"; - String eventTypeUpdated = TEST_EVENT_TYPE; - Profile profile = new Profile(TEST_PROFILE_ID); - Session session = new Session(sessionId, profile, new Date(), scope); - Event event = new Event(eventId, eventTypeOriginal, session, profile, scope, null, null, new Date()); - profileService.save(profile); - - keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID), - Objects::nonNull, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - - this.eventService.send(event); - - keepTrying("Event " + eventId + " not saved in the required time", () -> this.eventService.getEvent(eventId), - savedEvent -> Objects.nonNull(savedEvent) && eventTypeOriginal.equals(savedEvent.getEventType()), DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - - event.setEventType(eventTypeUpdated); //change the event so we can see the update effect - - //Act - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setSessionId(session.getItemId()); - contextRequest.setEvents(Arrays.asList(event)); - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); + assertEquals(TestUtils.executeContextJSONRequest(request, sessionId).getContextResponse().getProfileProperties().get("customProperty"), ("concealedValue")); + // set the property as concealed + customPropertyType.getMetadata().getSystemTags().add("concealed"); + profileService.deletePropertyType(customPropertyType.getItemId()); + profileService.setPropertyType(customPropertyType); + // Not in all properties request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request, sessionId); - - // Check event type did not changed - event = shouldBeTrueUntilEnd("Event type should not have changed", () -> eventService.getEvent(eventId), - (savedEvent) -> eventTypeOriginal.equals(savedEvent.getEventType()), DEFAULT_TRYING_TIMEOUT, 10); - assertEquals(1, event.getVersion().longValue()); - } - - @Test - public void testUpdateEventFromContextAuthorizedThirdPartyNoItemID_Fail() throws Exception { - //Arrange - String eventId = "test-event-id-" + System.currentTimeMillis(); - String sessionId = "test-session-id"; - String scope = TEST_SCOPE; - String eventTypeOriginal = "test-event-type-original"; - String eventTypeUpdated = TEST_EVENT_TYPE; - Session session = new Session(sessionId, profile, new Date(), scope); - Event event = new Event(eventId, eventTypeOriginal, session, profile, scope, null, null, new Date()); - this.eventService.send(event); - - keepTrying("Event " + eventId + " not saved in the required time", () -> this.eventService.getEvent(eventId), - savedEvent -> Objects.nonNull(savedEvent) && eventTypeOriginal.equals(savedEvent.getEventType()), DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - - event.setEventType(eventTypeUpdated); //change the event so we can see the update effect - - //Act - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setSessionId(session.getItemId()); - contextRequest.setEvents(Arrays.asList(event)); - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); + assertNull(TestUtils.executeContextJSONRequest(request, sessionId).getContextResponse().getProfileProperties().get("customProperty")); + // Got it explicitly + contextRequest.setRequiredProfileProperties(Arrays.asList("customProperty")); request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request, sessionId); - - // Check event type did not changed - event = shouldBeTrueUntilEnd("Event type should not have changed", () -> eventService.getEvent(eventId), - (savedEvent) -> eventTypeOriginal.equals(savedEvent.getEventType()), DEFAULT_TRYING_TIMEOUT, 10); - - assertEquals(1, event.getVersion().longValue()); - } - - @Test - public void testCreateEventsWithNoTimestampParam_profileAddedToSegment() throws Exception { - //Arrange - String sessionId = "test-session-id"; - String scope = TEST_SCOPE; - Event event = new Event(); - event.setEventType(TEST_EVENT_TYPE); - event.setScope(scope); - - //Act - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setSessionId(sessionId); - contextRequest.setRequireSegments(true); - contextRequest.setEvents(Arrays.asList(event)); - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - String cookieHeaderValue = TestUtils.executeContextJSONRequest(request, sessionId).getCookieHeaderValue(); - - refreshPersistence(Event.class); - - //Add the context-profile-id cookie to the second event - request.addHeader("Cookie", cookieHeaderValue); - ContextResponse response = (TestUtils.executeContextJSONRequest(request, sessionId)).getContextResponse(); //second event - - //Assert - assertEquals(1, response.getProfileSegments().size()); - assertThat(response.getProfileSegments(), hasItem(SEGMENT_ID)); - - } - - @Test - public void testCreateEventWithTimestampParam_pastEvent_profileIsNotAddedToSegment() throws Exception { - //Arrange - String sessionId = "test-session-id"; - String scope = TEST_SCOPE; - Event event = new Event(); - event.setEventType(TEST_EVENT_TYPE); - event.setScope(scope); - String regularURI = getFullUrl(CONTEXT_URL); - long oldTimestamp = LocalDateTime.now(ZoneId.of("UTC")).minusDays(SEGMENT_NUMBER_OF_DAYS + 1).toInstant(ZoneOffset.UTC) - .toEpochMilli(); - String customTimestampURI = regularURI + "?timestamp=" + oldTimestamp; - - //Act - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setSessionId(sessionId); - contextRequest.setRequireSegments(true); - contextRequest.setEvents(Arrays.asList(event)); - HttpPost request = new HttpPost(regularURI); - request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - //The first event is with a default timestamp (now) - String cookieHeaderValue = TestUtils.executeContextJSONRequest(request, sessionId).getCookieHeaderValue(); - //The second event is with a customized timestamp - request.setURI(URI.create(customTimestampURI)); - request.addHeader("Cookie", cookieHeaderValue); - ContextResponse response = (TestUtils.executeContextJSONRequest(request, sessionId)).getContextResponse(); //second event - - shouldBeTrueUntilEnd("Profile " + response.getProfileId() + " not found in the required time", - () -> profileService.load(response.getProfileId()), - (savedProfile) -> Objects.nonNull(savedProfile) && !savedProfile.getSegments().contains(SEGMENT_ID), DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - } - - @Test - public void testCreateEventWithTimestampParam_futureEvent_profileIsNotAddedToSegment() throws Exception { - //Arrange - String sessionId = "test-session-id"; - String scope = TEST_SCOPE; - Event event = new Event(); - event.setEventType(TEST_EVENT_TYPE); - event.setScope(scope); - String regularURI = getFullUrl(CONTEXT_URL); - long futureTimestamp = LocalDateTime.now(ZoneId.of("UTC")).plusDays(1).toInstant(ZoneOffset.UTC).toEpochMilli(); - String customTimestampURI = regularURI + "?timestamp=" + futureTimestamp; - - //Act - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setSessionId(sessionId); - contextRequest.setRequireSegments(true); - contextRequest.setEvents(Arrays.asList(event)); - HttpPost request = new HttpPost(regularURI); - request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - //The first event is with a default timestamp (now) - String cookieHeaderValue = TestUtils.executeContextJSONRequest(request, sessionId).getCookieHeaderValue(); - - //The second event is with a customized timestamp - request.setURI(URI.create(customTimestampURI)); - request.addHeader("Cookie", cookieHeaderValue); - ContextResponse response = TestUtils.executeContextJSONRequest(request, sessionId).getContextResponse(); //second event - - shouldBeTrueUntilEnd("Profile " + response.getProfileId() + " not found in the required time", - () -> profileService.load(response.getProfileId()), - (savedProfile) -> Objects.nonNull(savedProfile) && !savedProfile.getSegments().contains(SEGMENT_ID), DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - } - - @Test - public void testCreateEventWithProfileId_Success() throws Exception { - //Arrange - String eventId = "test-event-id-" + System.currentTimeMillis(); - String eventType = "test-event-type"; - Event event = new Event(); - event.setEventType(eventType); - event.setItemId(eventId); - - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setProfileId(TEST_PROFILE_ID); - contextRequest.setEvents(Arrays.asList(event)); - - //Act - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY); - request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request); - - keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID), - Objects::nonNull, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - } - - @Test - public void testCreateEventWithPropertiesValidation_Success() throws Exception { - //Arrange - String eventId = "valid-event-id-" + System.currentTimeMillis(); - String profileId = "valid-profile-id"; - String eventType = FLOAT_PROPERTY_EVENT_TYPE; - Event event = new Event(); - event.setEventType(eventType); - event.setItemId(eventId); - Map<String, Object> props = new HashMap<>(); - props.put("floatProperty", 3.14159); - event.setProperties(props); - - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setProfileId(profileId); - contextRequest.setEvents(Arrays.asList(event)); - - //Act - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY); - request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request); - - //Assert - event = keepTrying("Event not found", () -> eventService.getEvent(eventId), Objects::nonNull, DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - assertEquals(eventType, event.getEventType()); - assertEquals(3.14159, event.getProperty("floatProperty")); - } - - @Test - public void testCreateEventWithPropertyValueValidation_Failure() throws Exception { - //Arrange - String eventId = "invalid-event-value-id-" + System.currentTimeMillis(); - String profileId = "invalid-profile-id"; - String eventType = FLOAT_PROPERTY_EVENT_TYPE; - Event event = new Event(); - event.setEventType(eventType); - event.setItemId(eventId); - Map<String, Object> props = new HashMap<>(); - props.put("floatProperty", "Invalid value"); - event.setProperties(props); - - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setProfileId(profileId); - contextRequest.setEvents(Arrays.asList(event)); - - //Act - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY); + assertEquals(TestUtils.executeContextJSONRequest(request, sessionId).getContextResponse().getProfileProperties().get("customProperty"), ("concealedValue")); + // Got it with all + contextRequest.setRequiredProfileProperties(Arrays.asList("*", "customProperty")); request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request); + assertEquals(TestUtils.executeContextJSONRequest(request, sessionId).getContextResponse().getProfileProperties().get("customProperty"), ("concealedValue")); - //Assert - shouldBeTrueUntilEnd("Event should be null", () -> eventService.getEvent(eventId), Objects::isNull, DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - } - - @Test - public void testCreateEventWithPropertyNameValidation_Failure() throws Exception { - //Arrange - String eventId = "invalid-event-prop-id-" + System.currentTimeMillis(); - String profileId = "invalid-profile-id"; - Event event = new Event(); - event.setEventType(FLOAT_PROPERTY_EVENT_TYPE); - event.setItemId(eventId); - Map<String, Object> props = new HashMap<>(); - props.put("ffloatProperty", 3.14159); - event.setProperties(props); + // remove the concealed tag on the property type + customPropertyType.getMetadata().getSystemTags().remove("concealed"); + profileService.deletePropertyType(customPropertyType.getItemId()); + profileService.setPropertyType(customPropertyType); - ContextRequest contextRequest = new ContextRequest(); - contextRequest.setProfileId(profileId); - contextRequest.setEvents(Arrays.asList(event)); - - //Act - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY); + // Got it from all properties + contextRequest.setRequiredProfileProperties(Arrays.asList("*")); request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request); - - //Assert - shouldBeTrueUntilEnd("Event should be null", () -> eventService.getEvent(eventId), Objects::isNull, DEFAULT_TRYING_TIMEOUT, - DEFAULT_TRYING_TRIES); - } - - @Test - public void testOGNLVulnerability() throws Exception { - - File vulnFile = new File("target/vuln-file.txt"); - if (vulnFile.exists()) { - vulnFile.delete(); - } - String vulnFileCanonicalPath = vulnFile.getCanonicalPath(); - vulnFileCanonicalPath = vulnFileCanonicalPath.replace("\\", "\\\\"); // this is required for Windows support - - Map<String, String> parameters = new HashMap<>(); - parameters.put("VULN_FILE_PATH", vulnFileCanonicalPath); - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity( - new StringEntity(getValidatedBundleJSON("security/ognl-payload-1.json", parameters), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request); - - shouldBeTrueUntilEnd("Vulnerability successfully executed ! File created at " + vulnFileCanonicalPath, vulnFile::exists, - exists -> exists == Boolean.FALSE, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - } - - @Test - public void testMVELVulnerability() throws Exception { - - File vulnFile = new File("target/vuln-file.txt"); - if (vulnFile.exists()) { - vulnFile.delete(); - } - String vulnFileCanonicalPath = vulnFile.getCanonicalPath(); - vulnFileCanonicalPath = vulnFileCanonicalPath.replace("\\", "\\\\"); // this is required for Windows support - - Map<String, String> parameters = new HashMap<>(); - parameters.put("VULN_FILE_PATH", vulnFileCanonicalPath); - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity( - new StringEntity(getValidatedBundleJSON("security/mvel-payload-1.json", parameters), ContentType.APPLICATION_JSON)); - TestUtils.executeContextJSONRequest(request); - - shouldBeTrueUntilEnd("Vulnerability successfully executed ! File created at " + vulnFileCanonicalPath, vulnFile::exists, - exists -> exists == Boolean.FALSE, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - } - - @Test - public void testPersonalization() throws Exception { - - Map<String, String> parameters = new HashMap<>(); - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity(new StringEntity(getValidatedBundleJSON("personalization.json", parameters), ContentType.APPLICATION_JSON)); - TestUtils.RequestResponse response = TestUtils.executeContextJSONRequest(request); - assertEquals("Invalid response code", 200, response.getStatusCode()); - } - - @Test - public void testScorePersonalizationStrategy_Interests() throws Exception { - // Test request before adding interests to current profile. - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-score-interests.json", null), ContentType.APPLICATION_JSON)); - TestUtils.RequestResponse response = TestUtils.executeContextJSONRequest(request); - ContextResponse contextResponse = response.getContextResponse(); - List<String> variants = contextResponse.getPersonalizations().get("perso-by-interest"); - assertEquals("Invalid response code", 200, response.getStatusCode()); - assertEquals("Perso should be empty, profile is empty", 0, variants.size()); - variants = contextResponse.getPersonalizationResults().get("perso-by-interest").getContentIds(); - assertEquals("Perso should be empty, profile is empty", 0, variants.size()); - - // set profile for matching - Profile profile = profileService.load(TEST_PROFILE_ID); - profile.setProperty("age", 30); - profileService.save(profile); - keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID), - savedProfile -> (savedProfile != null && savedProfile.getProperty("age").equals(30)), DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - - // check results of the perso now - request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-score-interests.json", null), ContentType.APPLICATION_JSON)); - response = TestUtils.executeContextJSONRequest(request); - contextResponse = response.getContextResponse(); - variants = contextResponse.getPersonalizations().get("perso-by-interest"); - assertEquals("Invalid response code", 200, response.getStatusCode()); - assertEquals("Perso should contains the good number of variants", 1, variants.size()); - assertEquals("Variant is not the expected one", "matching-fishing-interests-custom-score-100-variant-expected-score-120", variants.get(0)); - variants = contextResponse.getPersonalizationResults().get("perso-by-interest").getContentIds(); - assertEquals("Perso should contains the good number of variants", 1, variants.size()); - assertEquals("Variant is not the expected one", "matching-fishing-interests-custom-score-100-variant-expected-score-120", variants.get(0)); - - // modify profile to add interests - profile = profileService.load(TEST_PROFILE_ID); - List<Map<String, Object>> interests = new ArrayList<>(); - Map<String, Object> interest1 = new HashMap<>(); - interest1.put("key", "cars"); - interest1.put("value", 50); - interests.add(interest1); - Map<String, Object> interest2 = new HashMap<>(); - interest2.put("key", "football"); - interest2.put("value", 40); - interests.add(interest2); - Map<String, Object> interest3 = new HashMap<>(); - interest3.put("key", "tennis"); - interest3.put("value", 30); - interests.add(interest3); - Map<String, Object> interest4 = new HashMap<>(); - interest4.put("key", "fishing"); - interest4.put("value", 20); - interests.add(interest4); - profile.setProperty("interests", interests); - profileService.save(profile); - keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID), - savedProfile -> (savedProfile != null && savedProfile.getProperty("interests") != null), DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - - // re test now that profiles has interests - request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-score-interests.json", null), ContentType.APPLICATION_JSON)); - response = TestUtils.executeContextJSONRequest(request); - contextResponse = response.getContextResponse(); - variants = contextResponse.getPersonalizations().get("perso-by-interest"); - assertEquals("Invalid response code", 200, response.getStatusCode()); - assertEquals("Perso should contains the good number of variants", 7, variants.size()); - assertEquals("Variant is not the expected one", "matching-fishing-interests-custom-score-100-variant-expected-score-120", variants.get(0)); - assertEquals("Variant is not the expected one", "matching-football-cars-interests-variant-expected-score-91", variants.get(1)); - assertEquals("Variant is not the expected one", "not-matching-football-cars-interests-variant-expected-score-90", variants.get(2)); - assertEquals("Variant is not the expected one", "not-matching-tennis-fishing-interests-variant-expected-score-50", variants.get(3)); - assertEquals("Variant is not the expected one", "matching-football-interests-variant-expected-score-51", variants.get(4)); - assertEquals("Variant is not the expected one", "matching-tennis-interests-variant-expected-score-31", variants.get(5)); - assertEquals("Variant is not the expected one", "not-matching-tennis-interests-custom-score-100-variant-expected-score-30", variants.get(6)); - variants = contextResponse.getPersonalizationResults().get("perso-by-interest").getContentIds(); - assertEquals("Perso should contains the good number of variants", 7, variants.size()); - assertEquals("Variant is not the expected one", "matching-fishing-interests-custom-score-100-variant-expected-score-120", variants.get(0)); - assertEquals("Variant is not the expected one", "matching-football-cars-interests-variant-expected-score-91", variants.get(1)); - assertEquals("Variant is not the expected one", "not-matching-football-cars-interests-variant-expected-score-90", variants.get(2)); - assertEquals("Variant is not the expected one", "not-matching-tennis-fishing-interests-variant-expected-score-50", variants.get(3)); - assertEquals("Variant is not the expected one", "matching-football-interests-variant-expected-score-51", variants.get(4)); - assertEquals("Variant is not the expected one", "matching-tennis-interests-variant-expected-score-31", variants.get(5)); - assertEquals("Variant is not the expected one", "not-matching-tennis-interests-custom-score-100-variant-expected-score-30", variants.get(6)); - } - - @Test - public void testRequireScoring() throws Exception { - - Map<String, String> parameters = new HashMap<>(); - String scoringSource = getValidatedBundleJSON("score1.json", parameters); - Scoring scoring = CustomObjectMapper.getObjectMapper().readValue(scoringSource, Scoring.class); - segmentService.setScoringDefinition(scoring); - - keepTrying("Profile does not contains scores in the required time", () -> profileService.load(TEST_PROFILE_ID), storedProfile -> - storedProfile.getScores() != null && storedProfile.getScores().get("score1") != null, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); - - // first let's make sure everything works without the requireScoring parameter - parameters = new HashMap<>(); - HttpPost request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity(new StringEntity(getValidatedBundleJSON("withoutRequireScores.json", parameters), ContentType.APPLICATION_JSON)); - TestUtils.RequestResponse response = TestUtils.executeContextJSONRequest(request); - assertEquals("Invalid response code", 200, response.getStatusCode()); - - assertNotNull("Context response should not be null", response.getContextResponse()); - Map<String, Integer> scores = response.getContextResponse().getProfileScores(); - assertNull("Context response should not contain scores", scores); - - // now let's test adding it. - parameters = new HashMap<>(); - request = new HttpPost(getFullUrl(CONTEXT_URL)); - request.setEntity(new StringEntity(getValidatedBundleJSON("withRequireScores.json", parameters), ContentType.APPLICATION_JSON)); - response = TestUtils.executeContextJSONRequest(request); - assertEquals("Invalid response code", 200, response.getStatusCode()); - - assertNotNull("Context response should not be null", response.getContextResponse()); - scores = response.getContextResponse().getProfileScores(); - assertNotNull("Context response should contain scores", scores); - assertNotNull("score1 not found in profile scores", scores.get("score1")); - assertEquals("score1 does not have expected value", 1, scores.get("score1").intValue()); - - segmentService.removeScoringDefinition(scoring.getItemId(), false); - } - - @Test - public void test_no_ControlGroup() throws Exception { - performPersonalizationWithControlGroup( - null, - Collections.singletonList("no-condition"), - false, - false, - null, - null); - } - - @Test - public void test_in_ControlGroup_profile_stored() throws Exception { - performPersonalizationWithControlGroup( - generateControlGroupConfig("false", "100.0"), - Arrays.asList("first-name-missing", "no-condition"), - true, - true, - true, - null); - - performPersonalizationWithControlGroup( - generateControlGroupConfig("false", "0.0"), - Arrays.asList("first-name-missing", "no-condition"), - true, - true, - true, - null); - } - - @Test - public void test_in_ControlGroup_session_stored() throws Exception { - performPersonalizationWithControlGroup( - generateControlGroupConfig("true", "100.0"), - Arrays.asList("first-name-missing", "no-condition"), - true, - true, - null, - true); - - performPersonalizationWithControlGroup( - generateControlGroupConfig("true", "0.0"), - Arrays.asList("first-name-missing", "no-condition"), - true, - true, - null, - true); - } - - @Test - public void test_out_ControlGroup_profile_stored() throws Exception { - performPersonalizationWithControlGroup( - generateControlGroupConfig("false", "0.0"), - Collections.singletonList("no-condition"), - true, - false, - false, - null); - - performPersonalizationWithControlGroup( - generateControlGroupConfig("false", "100.0"), - Collections.singletonList("no-condition"), - true, - false, - false, - null); - } - - @Test - public void test_out_ControlGroup_session_stored() throws Exception { - performPersonalizationWithControlGroup( - generateControlGroupConfig("true", "0.0"), - Collections.singletonList("no-condition"), - true, - false, - null, - false); - - performPersonalizationWithControlGroup( - generateControlGroupConfig("true", "100.0"), - Collections.singletonList("no-condition"), - true, - false, - null, - false); - } - - @Test - public void test_advanced_ControlGroup_test() throws Exception { - // STEP 1: start with no control group - performPersonalizationWithControlGroup( - null, - Collections.singletonList("no-condition"), - false, - false, - null, - null); - - // STEP 2: then enable control group stored in profile - performPersonalizationWithControlGroup( - generateControlGroupConfig("false", "100.0"), - Arrays.asList("first-name-missing", "no-condition"), - true, - true, - true, - null); - - // STEP 3: then re disable control group - performPersonalizationWithControlGroup( - null, - Collections.singletonList("no-condition"), - false, - false, - /* We can see we still have old control group check stored in the profile */ true, - null); - - // STEP 4: then re-enable control group, but session scoped this time, with a 0 percentage - performPersonalizationWithControlGroup( - generateControlGroupConfig("true", "0.0"), - Collections.singletonList("no-condition"), - true, - false, - /* We can see we still have old control group check stored in the profile */ true, - /* And now we also have a status saved in the session */ false); - - // STEP 5: then re-enable control group, but profile scoped this time, with a 0 percentage - // We should be in control group because of the STEP 2, the current profile already contains a persisted status for the perso. - // So even if current config is 0, old check already flagged current profile to be in the control group. - performPersonalizationWithControlGroup( - generateControlGroupConfig("false", "0.0"), - Arrays.asList("first-name-missing", "no-condition"), - true, - true, - /* We can see we still have old control group check stored in the profile */ true, - /* We can see we still have old control group check stored in the session too */ false); - - // STEP 6: then re-enable control group, but session scoped this time, with a 100 percentage - // We should not be in control group because of the STEP 4, the current session already contains a persisted status for the perso. - // So even if current config is 100, old check already flagged current profile to not be in the control group. - performPersonalizationWithControlGroup( - generateControlGroupConfig("true", "100.0"), - Collections.singletonList("no-condition"), - true, - false, - /* We can see we still have old control group check stored in the profile */ true, - /* We can see we still have old control group check stored in the session too */ false); - - // STEP 7: then re disable control group - performPersonalizationWithControlGroup( - null, - Collections.singletonList("no-condition"), - false, - false, - /* We can see we still have old control group check stored in the profile */ true, - /* We can see we still have old control group check stored in the session too */ false); + assertEquals(TestUtils.executeContextJSONRequest(request, sessionId).getContextResponse().getProfileProperties().get("customProperty"), ("concealedValue")); } private void performPersonalizationWithControlGroup(Map<String, String> controlGroupConfig, List<String> expectedVariants, diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java index 407eb5f87..df04e6d6c 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java @@ -42,13 +42,8 @@ import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; @WebService @Consumes(MediaType.APPLICATION_JSON) @@ -78,6 +73,8 @@ public class ContextJsonEndpoint { private RestServiceUtils restServiceUtils; @Reference private SchemaService schemaService; + @Reference + private ProfileService profileService; @OPTIONS @Path("/context.js") @@ -224,6 +221,12 @@ public class ContextJsonEndpoint { Map<String, Object> profileProperties = new HashMap<>(eventsRequestContext.getProfile().getProperties()); if (!contextRequest.getRequiredProfileProperties().contains("*")) { profileProperties.keySet().retainAll(contextRequest.getRequiredProfileProperties()); + } else { + // get public properties + explicit properties + Set<String> concealedProperties = profileService.getPropertyTypeBySystemTag("concealed").stream().map(Item::getItemId).collect(Collectors.toSet()); + // remove requested properties from the filtered properties + concealedProperties.removeAll(contextRequest.getRequiredProfileProperties().stream().filter(p -> !p.equals("*")).collect(Collectors.toList())); + profileProperties.keySet().removeAll(concealedProperties); } data.setProfileProperties(profileProperties); }