content: /*
   * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.activiti.runtime.api.impl;
  
  import static java.util.Arrays.asList;
  import static java.util.Collections.singletonMap;
  import static org.activiti.engine.impl.util.CollectionUtil.map;
  import static org.assertj.core.api.Assertions.assertThat;
  import static org.mockito.BDDMockito.given;
  import static org.mockito.Mockito.mock;
  
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.node.ObjectNode;
  import java.io.IOException;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import org.activiti.engine.ActivitiException;
  import org.activiti.engine.delegate.Expression;
  import org.activiti.engine.impl.el.ExpressionManager;
  import org.activiti.engine.impl.interceptor.DelegateInterceptor;
  import org.junit.jupiter.api.BeforeEach;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.extension.ExtendWith;
  import org.mockito.Mock;
  import org.mockito.junit.jupiter.MockitoExtension;
  
  @ExtendWith(MockitoExtension.class)
  public class ExpressionResolverTest {
  
      private ExpressionResolver expressionResolver;
  
      private ObjectMapper mapper = new ObjectMapper();
  
      @Mock
      private ExpressionManager expressionManager;
  
      @Mock
      private ExpressionEvaluator expressionEvaluator;
  
      @Mock
      private DelegateInterceptor delegateInterceptor;
  
      @BeforeEach
      public void setUp() {
          expressionResolver = new ExpressionResolver(expressionManager, mapper, delegateInterceptor);
      }
  
      @Test
      public void containsExpression_should_returnFalse_when_sourceIsNull() {
          //given
  
          //when
          boolean containsExpression = expressionResolver.containsExpression(null);
  
          //then
          assertThat(containsExpression).isFalse();
      }
  
      @Test
      public void containsExpression_should_returnFalse_when_stringDoesNotContainExpressionPattern() {
          //given
  
          //when
          boolean containsExpression = expressionResolver.containsExpression(
              "{just string with brackets, this is not an expression}"
          );
  
          //then
          assertThat(containsExpression).isFalse();
      }
  
      @Test
      public void containsExpression_should_returnTrue_when_stringContainsExpressionPattern() {
          //given
  
          //when
          boolean containsExpression = expressionResolver.containsExpression("${this is an expression}");
  
          //then
          assertThat(containsExpression).isTrue();
      }
  
      @Test
      public void containsExpression_should_returnFalse_when_ObjectNodeDoesNotContainExpressionPattern()
          throws Exception {
          //given
          ObjectNode objectNode = mapper.readValue("{\"name\":  \"Peter\"}", ObjectNode.class);
  
          //when
          boolean containsExpression = expressionResolver.containsExpression(objectNode);
  
          //then
          assertThat(containsExpression).isFalse();
      }
  
      @Test
      public void containsExpression_should_returnTrue_when_ObjectNodeContainsExpressionPattern() throws Exception {
          //given
          ObjectNode objectNode = mapper.readValue("{\"name\":  \"${name}\"}", ObjectNode.class);
  
          //when
          boolean containsExpression = expressionResolver.containsExpression(objectNode);
  
          //then
          assertThat(containsExpression).isTrue();
      }
  
      @Test
      public void containsExpression_should_returnFalse_when_MapValueDoesNotContainExpressionPattern() {
          //given
          Map<String, Integer> source = singletonMap("age", 10);
  
          //when
          boolean containsExpression = expressionResolver.containsExpression(source);
  
          //then
          assertThat(containsExpression).isFalse();
      }
  
      @Test
      public void containsExpression_should_returnTrue_when_MapValueContainsExpressionPattern() {
          //given
          Map<String, String> source = singletonMap("age", "${age}");
  
          //when
          boolean containsExpression = expressionResolver.containsExpression(source);
  
          //then
          assertThat(containsExpression).isTrue();
      }
  
      @Test
      public void containsExpression_should_returnFalse_when_ListDoesNotContainExpressionPattern() {
          //given
          List<String> source = asList("first", "second");
          //when
          boolean containsExpression = expressionResolver.containsExpression(source);
  
          //then
          assertThat(containsExpression).isFalse();
      }
  
      @Test
      public void containsExpression_should_returnTrue_when_ListContainsExpressionPattern() {
          //given
          List<String> source = asList("first", "${position}", "third");
          //when
          boolean containsExpression = expressionResolver.containsExpression(source);
  
          //then
          assertThat(containsExpression).isTrue();
      }
  
      @Test
      public void resolveExpressionsMap_should_replaceExpressionByValue_when_stringIsAnExpression() {
          //given
          Expression expression = buildExpression("${name}");
          given(expressionEvaluator.evaluate(expression, expressionManager, delegateInterceptor)).willReturn("John");
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("name", "${name}")
          );
          //then
          assertThat(result).containsEntry("name", "John");
      }
  
      @Test
      public void resolveExpressionsMap_should_replaceExpressionByValue_when_stringContainsAnExpression() {
          //given
          Expression nameExpression = buildExpression("${name}");
          given(expressionEvaluator.evaluate(nameExpression, expressionManager, delegateInterceptor)).willReturn("John");
  
          Expression placeExpression = buildExpression("${place}");
          given(expressionEvaluator.evaluate(placeExpression, expressionManager, delegateInterceptor)).willReturn(
              "London"
          );
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("welcomeMessage", "Welcome to ${place}, ${name}!")
          );
          //then
          assertThat(result).containsEntry("welcomeMessage", "Welcome to London, John!");
      }
  
      @Test
      public void resolveExpressionsMap_should_returnItself_when_stringIsEmpty() {
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("empty", "")
          );
  
          //then
          assertThat(result).containsEntry("empty", "");
      }
  
      @Test
      public void resolveExpressionsMap_should_returnItself_when_stringIsNull() {
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("empty", null)
          );
          //then
          assertThat(result).containsEntry("empty", null);
      }
  
      @Test
      public void resolveExpressionsMap_should_removeExpressionContent_when_notAbleToResolveExpressionInString() {
          //given
          Expression expression = buildExpression("${nonResolvableExpression}");
          given(expressionEvaluator.evaluate(expression, expressionManager, delegateInterceptor)).willThrow(
              new ActivitiException("Invalid property")
          );
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("result", "Welcome to ${nonResolvableExpression}!")
          );
          //then
          assertThat(result).containsEntry("result", "Welcome to !");
      }
  
      @Test
      public void resolveExpressionsMap_should_removeExpressionContent_when_notAbleToResolveIt() {
          //given
          Expression expression = buildExpression("${nonResolvableExpression}");
          given(expressionEvaluator.evaluate(expression, expressionManager, delegateInterceptor)).willThrow(
              new ActivitiException("Invalid property")
          );
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("result", "${nonResolvableExpression}")
          );
          //then
          assertThat(result).containsEntry("result", null);
      }
  
      @Test
      public void resolveExpressionsMap_should_replaceExpressionByValue_when_ObjectNodeContainsAnExpression()
          throws IOException {
          //given
          Expression nameExpression = buildExpression("${name}");
          given(expressionEvaluator.evaluate(nameExpression, expressionManager, delegateInterceptor)).willReturn("John");
  
          Expression placeExpression = buildExpression("${place}");
          given(expressionEvaluator.evaluate(placeExpression, expressionManager, delegateInterceptor)).willReturn(null);
  
          Expression ageExpression = buildExpression("${age}");
          given(expressionEvaluator.evaluate(ageExpression, expressionManager, delegateInterceptor)).willReturn(30);
  
          JsonNode node = mapper.readTree("{\"name\":\"${name}\",\"place\":\"${place}\",\"age\":\"${age}\"}");
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("node", node)
          );
          //then
          assertThat(result).containsEntry("node", map("name", "John", "place", null, "age", 30));
      }
  
      @Test
      public void resolveExpressionsMap_should_removeExpressionContent_when_ObjecNodeContainsAnExpressionUnableToBeResolved()
          throws IOException {
          //given
          Expression nameExpression = buildExpression("${name}");
          given(expressionEvaluator.evaluate(nameExpression, expressionManager, delegateInterceptor)).willThrow(
              new ActivitiException("Invalid property")
          );
  
          JsonNode node = mapper.readTree("{\"name\":\"${name}\",\"age\": 30}");
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("node", node)
          );
          //then
          assertThat(result).containsEntry("node", map("name", null, "age", 30));
      }
  
      @Test
      public void resolveExpressionsMap_should_replaceExpressionByValue_when_ListContainsAnExpression() {
          //given
          Expression placeExpression = buildExpression("${place}");
          given(expressionEvaluator.evaluate(placeExpression, expressionManager, delegateInterceptor)).willReturn(
              "London"
          );
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("places", asList("${place}", "Paris", "Berlin"))
          );
          //then
          assertThat(result).containsEntry("places", asList("London", "Paris", "Berlin"));
      }
  
      @Test
      public void resolveExpressionsMap_should_removeExpressionContent_when_ListContainsAnExpressionUnableToBeResolved() {
          //given
          Expression placeExpression = buildExpression("${place}");
          given(expressionEvaluator.evaluate(placeExpression, expressionManager, delegateInterceptor)).willThrow(
              new ActivitiException("Invalid property")
          );
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("places", asList("${place}", "Paris", "Berlin"))
          );
          //then
          assertThat(result).containsEntry("places", asList(null, "Paris", "Berlin"));
      }
  
      @Test
      public void resolveExpressionsMap_should_replaceExpressionByValue_when_MapContainsAnExpression() {
          //given
          Expression playerExpression = buildExpression("${player}");
          given(expressionEvaluator.evaluate(playerExpression, expressionManager, delegateInterceptor)).willReturn(
              "Agatha"
          );
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("players", map("Red", "John", "Green", "Peter", "Blue", "Mary", "Yellow", "${player}"))
          );
  
          //then
          assertThat(result).containsEntry(
              "players",
              map("Red", "John", "Green", "Peter", "Blue", "Mary", "Yellow", "Agatha")
          );
      }
  
      @Test
      public void resolveExpressionsMap_should_removeExpressionContent_when_MapContainsAnExpressionUnableToBeResolved() {
          //given
  
          Expression playerExpression = buildExpression("${player}");
          given(expressionEvaluator.evaluate(playerExpression, expressionManager, delegateInterceptor)).willThrow(
              new ActivitiException("Invalid property")
          );
  
          Map<String, Object> players = map("Red", "John", "Green", "Peter", "Blue", "Mary", "Yellow", "${player}");
  
          //when
          Map<String, Object> result = expressionResolver.resolveExpressionsMap(
              expressionEvaluator,
              singletonMap("players", players)
          );
  
          Map<String, Object> expectedResult = new HashMap<>(players);
          expectedResult.put("Yellow", null);
  
          //then
          assertThat(result).containsEntry("players", expectedResult);
      }
  
      private Expression buildExpression(String expressionContent) {
          Expression expression = mock(Expression.class);
          given(expressionManager.createExpression(expressionContent)).willReturn(expression);
          return expression;
      }
  }
  