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

Hash masking #677

Merged
merged 4 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/src/main/markdown/further.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ For an example of this usage, see the [`ExampleSystemTest.hashes()`][ExampleSyst

This test will fail if the expected hash values are not updated as the system model is changed, so the change reviewer can look at the diff on this test to see at a glance which parts of the system have been impacted by a change.

Note that [field-masking operations can be supplied][MessageHash.hashing(Actor,Include,Consumer)] when you specify your message hashes - this allows you remove or overwrite dynamic fields in your model to achieve consistent hashing results.

### Report diff tool

The flow framework uses an inheritance mechanism (deriving a new flow from an existing one) to effectively compress the set of test data that we use to exercise a tested system. This compression makes it easier to make sweeping changes to test data with small edits to the flow construction code, but by the same token it can be difficult to understand the impact of those small changes.
Expand All @@ -338,6 +340,7 @@ Full instructions for using the diff tool are provided [here](../../../../report

[MessageHash]: ../../../../validation/validation-core/src/main/java/com/mastercard/test/flow/validation/MessageHash.java
[ExampleSystemTest]: ../../../../example/app-model/src/test/java/com/mastercard/test/flow/example/app/model/ExampleSystemTest.java
[MessageHash.hashing(Actor,Include,Consumer)]: ../../../../validation/validation-core/src/main/java/com/mastercard/test/flow/validation/MessageHash.java#L140-L154,140-154

<!-- code_link_end -->

Expand Down
8 changes: 4 additions & 4 deletions message/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ Implementations of the Message interface

* [../flow](https://github.com/Mastercard/flow) Testing framework
* [message-core](message-core) Message implementation utilities
* [message-http](message-http) HypterText Transfer Protocol messages
* [message-json](message-json) JavaScript Object Notation Messages
* [message-http](message-http) HyperText Transfer Protocol messages
* [message-json](message-json) JavaScript Object Notation messages
* [message-sql](message-sql) Structured Query Language messages
* [message-text](message-text) Freeform text Message
* [message-text](message-text) Freeform text messages
* [message-web](message-web) Browser interaction messages
* [message-xml](message-xml) Extensible Markup Language Messages
* [message-xml](message-xml) Extensible Markup Language messages

<!-- title end -->

Expand Down
2 changes: 1 addition & 1 deletion message/message-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# message-http

HypterText Transfer Protocol messages
HyperText Transfer Protocol messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-http/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-http)

Expand Down
2 changes: 1 addition & 1 deletion message/message-http/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>message-http</artifactId>
<packaging>jar</packaging>
<description>HypterText Transfer Protocol messages</description>
<description>HyperText Transfer Protocol messages</description>

<dependencies>
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion message/message-json/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# message-json

JavaScript Object Notation Messages
JavaScript Object Notation messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-json/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-json)

Expand Down
2 changes: 1 addition & 1 deletion message/message-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>message-json</artifactId>
<packaging>jar</packaging>
<description>JavaScript Object Notation Messages</description>
<description>JavaScript Object Notation messages</description>

<dependencies>
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion message/message-text/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# message-text

Freeform text Message
Freeform text messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-text/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-text)

Expand Down
2 changes: 1 addition & 1 deletion message/message-text/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>message-text</artifactId>
<packaging>jar</packaging>
<description>Freeform text Message</description>
<description>Freeform text messages</description>

<dependencies>
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion message/message-xml/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# message-xml

Extensible Markup Language Messages
Extensible Markup Language messages

[![javadoc](https://javadoc.io/badge2/com.mastercard.test.flow/message-xml/javadoc.svg)](https://javadoc.io/doc/com.mastercard.test.flow/message-xml)

Expand Down
2 changes: 1 addition & 1 deletion message/message-xml/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>message-xml</artifactId>
<packaging>jar</packaging>
<description>Extensible Markup Language Messages</description>
<description>Extensible Markup Language messages</description>

<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public MessageHash hashing( String name,
Predicate<Interaction> interaction,
Function<Interaction, Stream<Message>> messages,
Function<Message, byte[]> content ) {
hashes.add( new Hash( name, flows, interaction, messages ) );
hashes.add( new Hash( name, flows, interaction, messages, content ) );
return this;
}

Expand Down Expand Up @@ -181,6 +181,29 @@ public MessageHash hashing( Actor responder, Include messages ) {
Message::content );
}

/**
* Adds a comparison to hash requests to and/or responses from a particular
* {@link Actor}, while masking out dynamic fields that should not be included
* in the hash.
*
* @param responder The actor
* @param messages The message type of interest
* @param mask How to mask out fields that should not be included in the
* hash
* @return <code>this</code>
*/
public MessageHash hashing( Actor responder, Include messages, Consumer<Message> mask ) {
return hashing( messages.name() + " " + messages.actorInfix + " " + responder.name(),
f -> true,
i -> i.responder() == responder,
messages,
m -> {
Message child = m.child();
mask.accept( child );
return child.content();
} );
}

/**
* Computes the actual hashes and compares them against the supplied
* expectations
Expand Down Expand Up @@ -226,13 +249,15 @@ private static class Hash {
public final Predicate<Flow> flows;
public final Predicate<Interaction> interaction;
public final Function<Interaction, Stream<Message>> messages;
public final Function<Message, byte[]> content;

public Hash( String name, Predicate<Flow> flows, Predicate<Interaction> interaction,
Function<Interaction, Stream<Message>> messages ) {
Function<Interaction, Stream<Message>> messages, Function<Message, byte[]> content ) {
this.name = name;
this.flows = flows;
this.interaction = interaction;
this.messages = messages;
this.content = content;
}

public void compute( Model model,
Expand All @@ -248,7 +273,7 @@ public void compute( Model model,
.flatMap( Flows::interactions )
.filter( interaction )
.flatMap( messages )
.map( Message::content )
.map( content )
.mapToInt( b -> {
byte[] h = digest.digest( b );
for( int i = 0; i < h.length; i++ ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package com.mastercard.test.flow.validation;

import static com.mastercard.test.flow.validation.MessageHash.Include.REQUESTS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

Expand All @@ -18,6 +25,7 @@
import com.mastercard.test.flow.Interaction;
import com.mastercard.test.flow.Message;
import com.mastercard.test.flow.Model;
import com.mastercard.test.flow.util.Bytes;
import com.mastercard.test.flow.validation.MessageHash.Include;

/**
Expand Down Expand Up @@ -127,6 +135,30 @@ void responses() {
"8D777F385D3DFEC8815D20F7496026DC 0001 4 B" );
}

/**
* Showing that when masking operations are requested, they are applied to a
* child of the messages in the model
*
* @throws NoSuchAlgorithmException This would be surprising
*/
@Test
void masking() throws NoSuchAlgorithmException {

String expectedHashedContent = "child of 'hash this!' with updates {dynamic field=static value}";
int expectedLength = expectedHashedContent.length();
MessageDigest digest = MessageDigest.getInstance( "MD5" );
String expectedHash = Bytes.toHex( digest.digest( expectedHashedContent.getBytes( UTF_8 ) ) );

MessageHash masking = new MessageHash( Assertions::assertEquals )
.hashing( BEN, REQUESTS, m -> m.set( "dynamic field", "static value" ) );

masking.expect( model(
flow( ntr( AVA, "hash this!", BEN, "but not this" ) ),
flow( ntr( BEN, "or this", CHE, "and definitley not this" ) ) ),
"REQUESTS --> BEN",
expectedHash + " 0001 " + expectedLength + " B" );
}

/**
* Demonstrates what happens when you have even numbers of identical messages
* being hashed together
Expand Down Expand Up @@ -270,6 +302,27 @@ private static Interaction ntr( Actor requester, String req, Actor responder, St
private static Message msg( String content ) {
Message msg = mock( Message.class );
when( msg.content() ).thenReturn( content.getBytes( UTF_8 ) );

Map<String, String> childUpdates = new TreeMap<>();
Message child = mock( Message.class );

doAnswer( inv -> {
childUpdates.put( inv.getArgument( 0 ), inv.getArgument( 1 ) );
return null;
} )
.when( child )
.set( any(), any() );

when( child.content() )
.thenAnswer( inv -> {
String s = String.format(
"child of '%s' with updates %s",
content, childUpdates );
return s.getBytes( UTF_8 );
} );

when( msg.child() ).thenReturn( child );

return msg;
}
}