Skip to content

Commit

Permalink
Standardize dynamic variables API
Browse files Browse the repository at this point in the history
  • Loading branch information
valfirst committed Jan 13, 2022
1 parent 870cf69 commit 73c2f6a
Show file tree
Hide file tree
Showing 48 changed files with 731 additions and 495 deletions.
1 change: 1 addition & 0 deletions vividus-engine/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ dependencies {
testImplementation(group: 'org.junit.jupiter', name: 'junit-jupiter')
testImplementation(group: 'org.mockito', name: 'mockito-junit-jupiter', version: versions.mockito)
testImplementation(group: 'com.github.valfirst', name: 'slf4j-test', version: versions.slf4jTest)
testImplementation(group: 'nl.jqno.equalsverifier', name: 'equalsverifier', version: '3.8.2')
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -25,12 +25,16 @@

import org.apache.commons.lang3.StringUtils;
import org.jbehave.core.embedder.StoryControls;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vividus.DryRunAwareExecutor;
import org.vividus.context.VariableContext;
import org.vividus.variable.DynamicVariable;

public class VariableResolver implements DryRunAwareExecutor
{
private static final Logger LOGGER = LoggerFactory.getLogger(VariableResolver.class);

private static final String VARIABLE_START_MARKER = "${";
private static final String LINE_BREAKS = "[\r\n]*";

Expand Down Expand Up @@ -156,13 +160,21 @@ private Object getVariableValue(String variableKey)
Object variable = variableContext.getVariable(variableKey);
if (variable == null)
{
variable = execute(() -> Optional.ofNullable(dynamicVariables.get(variableKey))
.map(DynamicVariable::getValue)
.orElse(null), null);
variable = execute(() -> calculateDynamicVariableValue(variableKey), null);
}
return variable;
}

private String calculateDynamicVariableValue(String variableKey)
{
return Optional.ofNullable(dynamicVariables.get(variableKey))
.map(DynamicVariable::calculateValue)
.flatMap(r -> r.getValueOrHandleError(
error -> LOGGER.error("Unable to resolve dynamic variable ${{}}: {}", variableKey, error)
))
.orElse(null);
}

@Override
public StoryControls getStoryControls()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,8 +16,7 @@

package org.vividus.variable;

@FunctionalInterface
public interface DynamicVariable
{
String getValue();
DynamicVariableCalculationResult calculateValue();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2019-2022 the original author or authors.
*
* 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
*
* https://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.vividus.variable;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

public final class DynamicVariableCalculationResult
{
private final Optional<String> value;
private final Optional<String> error;

private DynamicVariableCalculationResult(Optional<String> value, Optional<String> error)
{
this.value = value;
this.error = error;
}

public static DynamicVariableCalculationResult withValue(String value)
{
return new DynamicVariableCalculationResult(Optional.of(value), Optional.empty());
}

public static DynamicVariableCalculationResult withError(String error)
{
return new DynamicVariableCalculationResult(Optional.empty(), Optional.of(error));
}

public static DynamicVariableCalculationResult withValueAndError(Optional<String> value,
Supplier<String> errorSupplier)
{
return new DynamicVariableCalculationResult(value,
value.isEmpty() ? Optional.of(errorSupplier.get()) : Optional.empty());
}

public Optional<String> getValueOrHandleError(Consumer<String> errorHandler)
{
if (!value.isPresent())
{
errorHandler.accept(error.get());
}
return value;
}

@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
DynamicVariableCalculationResult that = (DynamicVariableCalculationResult) o;
return value.equals(that.value) && error.equals(that.error);
}

@Override
public int hashCode()
{
return Objects.hash(value, error);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,6 +35,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.vividus.context.VariableContext;
import org.vividus.variable.DynamicVariable;
import org.vividus.variable.DynamicVariableCalculationResult;

@ExtendWith(MockitoExtension.class)
class VariableResolverTests
Expand Down Expand Up @@ -83,7 +84,7 @@ void shouldResolveVariables(String valueToResolve, String variableKey, String va
})
void shouldResolveVariableAsObject()
{
Object object = new Object();
var object = new Object();
when(variableContext.getVariable("varWithObject")).thenReturn(object);
assertEquals(object, convert("${varWithObject}"));
}
Expand Down Expand Up @@ -144,22 +145,20 @@ void shouldResolveDefaultValueWithVariable()
})
void testVariablesDynamicValue(String key, String alias)
{
DynamicVariable dynamicVariable = mock(DynamicVariable.class);
var dynamicVariable = mock(DynamicVariable.class);
when(dynamicVariable.calculateValue()).thenReturn(DynamicVariableCalculationResult.withValue(VALUE1));
when(variableContext.getVariable(key)).thenReturn(null);
when(variableContext.getVariable(alias)).thenReturn(null);
VariableResolver variableResolver = new VariableResolver(variableContext, Map.of(key, dynamicVariable),
storyControls);
when(dynamicVariable.getValue()).thenReturn(VALUE1);
var variableResolver = new VariableResolver(variableContext, Map.of(key, dynamicVariable), storyControls);
assertEquals(VALUE1, variableResolver.resolve(asRef(key)));
assertEquals(VALUE1, variableResolver.resolve(asRef(alias)));
}

@Test
void shouldNotResolveDynamicVariablesIfTheExecutionIsDryRun()
{
DynamicVariable dynamicVariable = mock(DynamicVariable.class);
VariableResolver variableResolver = new VariableResolver(variableContext, Map.of(KEY, dynamicVariable),
storyControls);
var dynamicVariable = mock(DynamicVariable.class);
var variableResolver = new VariableResolver(variableContext, Map.of(KEY, dynamicVariable), storyControls);
when(storyControls.dryRun()).thenReturn(true);
variableResolver.resolve(asRef(KEY));
verifyNoInteractions(dynamicVariable);
Expand All @@ -168,13 +167,12 @@ void shouldNotResolveDynamicVariablesIfTheExecutionIsDryRun()
@Test
void testVariablesDynamicValueNoProvider()
{
DynamicVariable dynamicVariable = mock(DynamicVariable.class);
var dynamicVariable = mock(DynamicVariable.class);
when(variableContext.getVariable(VAR1)).thenReturn(null);
VariableResolver variableResolver = new VariableResolver(variableContext, Map.of(VAR2, dynamicVariable),
storyControls);
String test1 = asRef(VAR1);
Object actualValue = variableResolver.resolve(test1);
assertEquals(test1, actualValue);
var variableResolver = new VariableResolver(variableContext, Map.of(VAR2, dynamicVariable), storyControls);
String varReference = asRef(VAR1);
Object actualValue = variableResolver.resolve(varReference);
assertEquals(varReference, actualValue);
}

private static String asRef(String name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2019-2022 the original author or authors.
*
* 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
*
* https://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.vividus.variable;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;

import java.util.Optional;
import java.util.function.Consumer;

import org.junit.jupiter.api.Test;

import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;

class DynamicVariableCalculationResultTests
{
private static final String VALUE = "value";
private static final String ERROR = "error";

@Test
void shouldCreateResultWithValueAndWithoutError()
{
var result = DynamicVariableCalculationResult.withValue(VALUE);
assertResultWithValue(result, VALUE);
}

@Test
void shouldCreateResultWithoutValueAndWithError()
{
var result = DynamicVariableCalculationResult.withError(ERROR);
assertResultWithError(result, ERROR);
}

@Test
void shouldCreateResultWithOptionalValueAndWithoutError()
{
var result = DynamicVariableCalculationResult.withValueAndError(Optional.of(VALUE), () -> ERROR);
assertResultWithValue(result, VALUE);
}

@Test
void shouldCreateResultWithMissingValueAndWithError()
{
var result = DynamicVariableCalculationResult.withValueAndError(Optional.empty(), () -> ERROR);
assertResultWithError(result, ERROR);
}

@Test
void validateHashCodeAndEquals()
{
EqualsVerifier.forClass(DynamicVariableCalculationResult.class)
.suppress(Warning.NULL_FIELDS)
.verify();
}

@SuppressWarnings("unchecked")
private void assertResultWithValue(DynamicVariableCalculationResult result, String expectedValue)
{
assertAll(
() -> {
var errorHandler = mock(Consumer.class);
assertEquals(Optional.of(expectedValue), result.getValueOrHandleError(errorHandler));
verifyNoInteractions(errorHandler);
},
() -> assertEquals(result, DynamicVariableCalculationResult.withValue(expectedValue))
);
}

@SuppressWarnings("unchecked")
private void assertResultWithError(DynamicVariableCalculationResult result, String expectedError)
{
assertAll(
() -> {
var errorHandler = mock(Consumer.class);
assertEquals(Optional.empty(), result.getValueOrHandleError(errorHandler));
verify(errorHandler).accept(expectedError);
},
() -> assertEquals(result, DynamicVariableCalculationResult.withError(expectedError)));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,38 +14,37 @@
* limitations under the License.
*/

package org.vividus.variable.ui;
package org.vividus.ui.variable;

import java.util.Optional;
import java.util.function.Function;
import java.util.function.ToIntFunction;

import org.openqa.selenium.Rectangle;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vividus.ui.context.UiContext;
import org.vividus.variable.DynamicVariable;
import org.vividus.variable.DynamicVariableCalculationResult;

public abstract class AbstractContextProvidingDynamicVariable implements DynamicVariable
public abstract class AbstractSearchContextRectangleDynamicVariable implements DynamicVariable
{
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractContextProvidingDynamicVariable.class);

private final UiContext uiContext;
private final ToIntFunction<Rectangle> valueProvider;

public AbstractContextProvidingDynamicVariable(UiContext uiContext)
protected AbstractSearchContextRectangleDynamicVariable(UiContext uiContext, ToIntFunction<Rectangle> valueProvider)
{
this.uiContext = uiContext;
this.valueProvider = valueProvider;
}

protected String getContextRectValue(Function<Rectangle, Integer> valueProvider)
@Override
public DynamicVariableCalculationResult calculateValue()
{
return Optional.ofNullable(uiContext.getSearchContext(WebElement.class))
return DynamicVariableCalculationResult.withValueAndError(
Optional.ofNullable(uiContext.getSearchContext(WebElement.class))
.map(WebElement::getRect)
.map(valueProvider::apply)
.map(String::valueOf)
.orElseGet(() -> {
LOGGER.atError().log("Unable to get coordinate, context is not set");
return "-1";
});
.map(valueProvider::applyAsInt)
.map(String::valueOf),
() -> "the search context is not set"
);
}
}
Loading

0 comments on commit 73c2f6a

Please # to comment.