Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Usage updates #79

Merged
merged 11 commits into from
Sep 13, 2022
30 changes: 30 additions & 0 deletions api/src/main/java/com/mastercard/test/flow/util/Flows.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
Expand Down Expand Up @@ -70,6 +72,34 @@ public static Stream<Interaction> interactions( Flow flow ) {
return Stream.concat( Stream.of( flow.root() ), descendents( flow.root() ) );
}

/**
* Looks for an {@link Interaction} in a {@link Flow}
*
* @param flow The {@link Flow} to search
* @param interaction How to recognise the {@link Interaction} of interest
* @return The chronologically first {@link Interaction} in the {@link Flow}
* that satisfies the supplied condition
* @see InteractionPredicate
*/
public static Optional<Interaction> find( Flow flow, Predicate<Interaction> interaction ) {
return interactions( flow ).filter( interaction ).findFirst();
}

/**
* Extracts a single {@link Interaction} from a {@link Flow}
*
* @param flow The {@link Flow} to search
* @param interaction How to recognise the {@link Interaction} of interest
* @return The chronologically first {@link Interaction} in the {@link Flow}
* that satisfies the supplied condition
* @throws IllegalArgumentException if there is no such {@link Interaction}
* @see InteractionPredicate
*/
public static Interaction get( Flow flow, Predicate<Interaction> interaction ) {
return find( flow, interaction ).orElseThrow( () -> new IllegalArgumentException(
"No interaction matching '" + interaction + "' in " + flow.meta().id() ) );
}

/**
* Collects the consequences of an interaction
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.mastercard.test.flow.util;

import static java.util.Collections.emptySet;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;

import com.mastercard.test.flow.Actor;
import com.mastercard.test.flow.Interaction;

/**
* <p>
* A convenience implementation of {@link Predicate} that offers friendlier
* {@link #toString()} behaviour.
* </p>
* <p>
* Note that this type is immutable - the methods that appear to be mutators
* actually return new instances.
* </p>
*/
public class InteractionPredicate implements Predicate<Interaction> {

private final Set<Actor> tx;
private final Set<Actor> rx;
private final Set<String> include;
private final Set<String> exclude;

/**
* Constructs an empty {@link Predicate} that will match every
* {@link Interaction}
*/
public InteractionPredicate() {
this( emptySet(), emptySet(), emptySet(), emptySet() );
}

private InteractionPredicate( Set<Actor> tx, Set<Actor> rx, Set<String> include,
Set<String> exclude ) {
this.tx = tx;
this.rx = rx;
this.include = include;
this.exclude = exclude;
}

/**
* Defines the allowed set of requesting {@link Actor}s
*
* @param senders {@link Interaction}s that originate from these {@link Actor}s
* will be matched
* @return A new {@link InteractionPredicate} based on <code>this</code>, but
* with the new condition
*/
public InteractionPredicate from( Actor... senders ) {
Set<Actor> nf = new TreeSet<>( comparing( Actor::name ) );
Collections.addAll( nf, senders );
return new InteractionPredicate( nf, rx, include, exclude );
}

/**
* Defines the allowed set of responding {@link Actor}s
*
* @param receivers interactions that destinate(?) at the {@link Actor}s will be
* matched
* @return A new {@link InteractionPredicate} based on <code>this</code>, but
* with the new condition
*/
public InteractionPredicate to( Actor... receivers ) {
Set<Actor> nt = new TreeSet<>( comparing( Actor::name ) );
Collections.addAll( nt, receivers );
return new InteractionPredicate( tx, nt, include, exclude );
}

/**
* Defines the set of tags that must be present
*
* @param tags interactions that bear all of these tags will be matched
* @return A new {@link InteractionPredicate} based on <code>this</code>, but
* with the new condition
*/
public InteractionPredicate with( String... tags ) {
Set<String> ni = new TreeSet<>();
Collections.addAll( ni, tags );
return new InteractionPredicate( tx, rx, ni, exclude );
}

/**
* Defines the set of tags that must be absent
*
* @param tags interactions that bear none of these tags will be matched
* @return A new {@link InteractionPredicate} based on <code>this</code>, but
* with the new condition
*/
public InteractionPredicate without( String... tags ) {
Set<String> ne = new TreeSet<>();
Collections.addAll( ne, tags );
return new InteractionPredicate( tx, rx, include, ne );
}

@Override
public boolean test( Interaction t ) {
boolean match = true;
if( !tx.isEmpty() ) {
match &= tx.contains( t.requester() );
}
if( !rx.isEmpty() ) {
match &= rx.contains( t.responder() );
}
if( !include.isEmpty() ) {
match &= include.stream().allMatch( t.tags()::contains );
}
if( !exclude.isEmpty() ) {
match &= exclude.stream().noneMatch( t.tags()::contains );
}
return match;
}

@Override
public String toString() {
List<String> conditions = new ArrayList<>();
if( !tx.isEmpty() ) {
conditions.add( "from " + tx );
}
if( !rx.isEmpty() ) {
conditions.add( "to " + rx );
}
if( !include.isEmpty() ) {
conditions.add( "has tags " + include );
}
if( !exclude.isEmpty() ) {
conditions.add( "lacks tags " + exclude );
}

if( conditions.isEmpty() ) {
return "anything";
}

return conditions.stream().collect( joining( " and " ) );
}
}
62 changes: 56 additions & 6 deletions api/src/test/java/com/mastercard/test/flow/util/FlowsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.lang.reflect.Constructor;
Expand All @@ -13,6 +15,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -82,14 +85,14 @@ private static final Actor actor( String name ) {

private static final Interaction interaction( Actor from, String request,
Actor to, String response ) {
Interaction m = Mockito.mock( Interaction.class );
Mockito.when( m.requester() ).thenReturn( from );
Interaction ntr = Mockito.mock( Interaction.class );
Mockito.when( ntr.requester() ).thenReturn( from );
Message req = message( request );
Mockito.when( m.request() ).thenReturn( req );
Mockito.when( m.responder() ).thenReturn( to );
Mockito.when( ntr.request() ).thenReturn( req );
Mockito.when( ntr.responder() ).thenReturn( to );
Message res = message( response );
Mockito.when( m.response() ).thenReturn( res );
return m;
Mockito.when( ntr.response() ).thenReturn( res );
return ntr;
}

private static final Message message( String name ) {
Expand Down Expand Up @@ -191,6 +194,53 @@ void interactions() {
.collect( joining( "\n" ) ) );
}

/**
* Exercises {@link Flows#find(Flow, java.util.function.Predicate)}
*/
@Test
void find() {
Flow flow = flow( "flow", "", null );

assertFalse( Flows.find( flow, i -> false ).isPresent() );

assertEquals( "AVA > abc | BEN < cba",
Flows.find( flow, i -> true )
.map( i -> String.format( "%s > %s | %s < %s",
i.requester().name(), i.request().assertable(),
i.responder().name(), i.response().assertable() ) )
.get() );
}

/**
* Exercises {@link Flows#get(Flow, java.util.function.Predicate)}
*/
@Test
void get() {
Flow flow = flow( "flow", "", null );

Interaction i = Flows.get( flow, n -> true );
assertEquals( "AVA > abc | BEN < cba",
String.format( "%s > %s | %s < %s",
i.requester().name(), i.request().assertable(),
i.responder().name(), i.response().assertable() ) );

Predicate<Interaction> none = new Predicate<Interaction>() {
@Override
public boolean test( Interaction t ) {
return false;
}

@Override
public String toString() {
return "predicate tostring";
}
};

IllegalArgumentException iae = assertThrows( IllegalArgumentException.class,
() -> Flows.get( flow, none ) );
assertEquals( "No interaction matching 'predicate tostring' in flow []", iae.getMessage() );
}

/**
* Exercises {@link Flows#transmissions(Flow)}
*/
Expand Down
Loading