From a9731b69481e76f7378c9fd8c7d2912a88037321 Mon Sep 17 00:00:00 2001 From: Ryan McNally Date: Tue, 13 Aug 2024 21:48:05 +0100 Subject: [PATCH] basic testing in place --- README.md | 3 +- .../flowty/bowlby/app/srv/LinkHandler.java | 2 +- .../dev/flowty/bowlby/model/BowlbySystem.java | 6 +- .../bowlby/model/flow/ArtifactLink.java | 132 ++++++++++++++++++ .../{Latest.java => LatestFromWorkflow.java} | 4 +- .../flowty/bowlby/model/msg/HttpMessage.java | 20 +++ .../flowty/bowlby/model/msg/WebMessage.java | 9 ++ .../test/java/dev/flowty/bowlby/it/ApiIT.java | 5 +- 8 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 model/src/main/java/dev/flowty/bowlby/model/flow/ArtifactLink.java rename model/src/main/java/dev/flowty/bowlby/model/flow/{Latest.java => LatestFromWorkflow.java} (99%) diff --git a/README.md b/README.md index 15fe64c..c1ded88 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ github actions artifact proxy # TODO - 1. Tests 1. better browser control in testing - headless default, stepping, etc 1. GUI for local use + 1. Wait for about 90 days for the artifact-link tests to fail, then fix them + diff --git a/app/src/main/java/dev/flowty/bowlby/app/srv/LinkHandler.java b/app/src/main/java/dev/flowty/bowlby/app/srv/LinkHandler.java index bcb04b8..c6fa28e 100644 --- a/app/src/main/java/dev/flowty/bowlby/app/srv/LinkHandler.java +++ b/app/src/main/java/dev/flowty/bowlby/app/srv/LinkHandler.java @@ -47,7 +47,7 @@ public void handle( HttpExchange exchange ) throws IOException { Matcher artifactMatch = GITHUB_ARTIFACT_LINK.matcher( link ); if( artifactMatch.find() ) { ServeUtil.redirect( exchange, String.format( - "/artifacts/%s/%s/%s", + "/artifacts/%s/%s/%s/", artifactMatch.group( 1 ), artifactMatch.group( 2 ), artifactMatch.group( 3 ) ) ); return; } diff --git a/model/src/main/java/dev/flowty/bowlby/model/BowlbySystem.java b/model/src/main/java/dev/flowty/bowlby/model/BowlbySystem.java index 6d57e96..4e99653 100644 --- a/model/src/main/java/dev/flowty/bowlby/model/BowlbySystem.java +++ b/model/src/main/java/dev/flowty/bowlby/model/BowlbySystem.java @@ -5,8 +5,9 @@ import com.mastercard.test.flow.Unpredictable; import com.mastercard.test.flow.model.LazyModel; +import dev.flowty.bowlby.model.flow.ArtifactLink; import dev.flowty.bowlby.model.flow.Index; -import dev.flowty.bowlby.model.flow.Latest; +import dev.flowty.bowlby.model.flow.LatestFromWorkflow; /** * Models the expected behaviour of the system @@ -59,7 +60,8 @@ public enum Unpredictables implements Unpredictable { */ public static final Model MODEL = new LazyModel( "Bowlby" ) .with( Index.class ) - .with( Latest.class ) + .with( LatestFromWorkflow.class ) + .with( ArtifactLink.class ) ; } diff --git a/model/src/main/java/dev/flowty/bowlby/model/flow/ArtifactLink.java b/model/src/main/java/dev/flowty/bowlby/model/flow/ArtifactLink.java new file mode 100644 index 0000000..09e0478 --- /dev/null +++ b/model/src/main/java/dev/flowty/bowlby/model/flow/ArtifactLink.java @@ -0,0 +1,132 @@ +package dev.flowty.bowlby.model.flow; + +import com.mastercard.test.flow.Flow; +import com.mastercard.test.flow.builder.Chain; +import com.mastercard.test.flow.builder.Creator; +import com.mastercard.test.flow.builder.Deriver; +import com.mastercard.test.flow.model.EagerModel; +import com.mastercard.test.flow.util.TaggedGroup; +import com.mastercard.test.flow.util.Tags; + +import dev.flowty.bowlby.model.BowlbySystem.Actors; +import dev.flowty.bowlby.model.msg.ApiMessage; +import dev.flowty.bowlby.model.msg.ArtifactMessage; +import dev.flowty.bowlby.model.msg.ArtifactMessage.Artifact; +import dev.flowty.bowlby.model.msg.HttpMessage; +import dev.flowty.bowlby.model.msg.WebMessage; +import dev.flowty.bowlby.model.msg.ntr.Interactions; + +/** + * Flows that explore the browse-this-artifact behaviour + */ +public class ArtifactLink extends EagerModel { + + /***/ + public static final TaggedGroup MODEL_TAGS = new TaggedGroup( "chain:workflow" ) + .union( "200", "303", "artifact" ); + + /*** + * @param index provides the basis for our form retrieval + */ + public ArtifactLink( Index index ) { + super( MODEL_TAGS ); + + Chain chain = new Chain( "artifact" ); + + Flow get = Deriver.build( index.get, + flow -> flow + .meta( data -> data + .description( "form" ) + .motivation( + """ + This chain demonstrates the ability to browse artifact files. + We start by visiting the bowlby root page, which offers a form that accepts links to github artifacts.""" ) ) + .prerequisite( index.get ) + .removeCall( Interactions.FAVICON ), + chain ); + + Flow submit = Creator.build( flow -> flow + .meta( data -> data + .description( "submit" ) + .tags( Tags.add( "302", "200", "artifact" ) ) + .motivation( + """ + Submitting an artifact link via the form.\ + Bowbly will return a 302 redirect to the url at which the artifact can be browsed""" ) ) + .prerequisite( get ) + .call( a -> a + .from( Actors.USER ) + .to( Actors.BROWSER ) + .request( WebMessage.submit( + "https://github.com/therealryan/bowlby/actions/runs/10334399684/artifacts/1798279626" ) ) + .call( b -> b.to( Actors.BOWLBY ) + .tags( Tags.add( "submit" ) ) + .request( HttpMessage.chromeRequest( "GET", "" + + "/?link=https%3A%2F%2Fgithub.com%2Ftherealryan%2Fbowlby%2Factions%2Fruns%2F10334399684%2Fartifacts%2F1798279626" ) ) + .response( HttpMessage.redirectResponse( + "/artifacts/therealryan/bowlby/1798279626/" ) ) ) + .call( b -> b.to( Actors.BOWLBY ) + .tags( Tags.add( "download" ) ) + .request( HttpMessage.chromeRequest( "GET", + "/artifacts/therealryan/bowlby/1798279626/" ) ) + .call( c -> c.to( Actors.GITHUB ) + .request( ApiMessage.request( + "/repos/therealryan/bowlby/actions/artifacts/1798279626/zip" ) ) + .response( ApiMessage.artifactRedirect() ) ) + .call( c -> c.to( Actors.ARTIFACTS ) + .request( ArtifactMessage.get( "/a/very/long/url" ) ) + .response( ArtifactMessage.data( Artifact.BETA ) ) ) + .response( HttpMessage.directoryListing( "file.txt", "subdir/" ) ) ) + .response( WebMessage.summarise() + .set( "forms", "" ) + .set( "header", "[bowlby](http://[::]:56567/)" ) + .set( "lists", + """ + [file.txt](http://[::]:56567/artifacts/therealryan/bowlby/1798279626/file.txt) + [subdir/](http://[::]:_masked_/artifacts/therealryan/bowlby/_masked_/subdir/)""" ) + .set( "url", "http://[::]:56567/artifacts/therealryan/bowlby/1798279626/" ) ) ), + chain ); + + Flow dir = Creator.build( flow -> flow.meta( data -> data + .description( "dir" ) + .motivation( "Clicking through to view the subdirectory" ) ) + .prerequisite( submit ) + .call( a -> a + .from( Actors.USER ) + .to( Actors.BROWSER ) + .request( WebMessage.clickLink( "subdir/" ) ) + .call( b -> b.to( Actors.BOWLBY ) + .request( HttpMessage.chromeRequest( "GET", + "/artifacts/therealryan/bowlby/1798279626/subdir/" ) ) + .response( HttpMessage.directoryListing( "../", "subfile.txt" ) ) ) + .response( WebMessage.summarise() + .set( "forms", "" ) + .set( "header", "[bowlby](http://[::]:56567/)" ) + .set( "lists", + """ + [../](http://[::]:56567/artifacts/therealryan/bowlby/1798279626/) + [subfile.txt](http://[::]:_masked_/artifacts/therealryan/bowlby/_masked_/subdir/subfile.txt)""" ) + .set( "url", + "http://[::]:56567/artifacts/therealryan/bowlby/1798279626/subdir/" ) ) ), + chain ); + + Flow file = Creator.build( flow -> flow.meta( data -> data + .description( "file" ) + .motivation( "Clicking through to view a file" ) ) + .prerequisite( dir ) + .call( a -> a + .from( Actors.USER ) + .to( Actors.BROWSER ) + .request( WebMessage.clickLink( "subfile.txt" ) ) + .call( b -> b.to( Actors.BOWLBY ) + .request( HttpMessage.chromeRequest( "GET", + "/artifacts/therealryan/bowlby/1798279626/subdir/subfile.txt" ) ) + .response( HttpMessage.textResponse( + "This is just a text file in a subdirectory!\n" ) ) ) + .response( WebMessage.text() + .set( "text", "This is just a text file in a subdirectory!" ) ) ), + chain ); + + members( flatten( get, submit, dir, file ) ); + } +} diff --git a/model/src/main/java/dev/flowty/bowlby/model/flow/Latest.java b/model/src/main/java/dev/flowty/bowlby/model/flow/LatestFromWorkflow.java similarity index 99% rename from model/src/main/java/dev/flowty/bowlby/model/flow/Latest.java rename to model/src/main/java/dev/flowty/bowlby/model/flow/LatestFromWorkflow.java index 64cf60d..06c6ff3 100644 --- a/model/src/main/java/dev/flowty/bowlby/model/flow/Latest.java +++ b/model/src/main/java/dev/flowty/bowlby/model/flow/LatestFromWorkflow.java @@ -21,7 +21,7 @@ /** * Flows that explore the get-latest-artifact behaviour */ -public class Latest extends EagerModel { +public class LatestFromWorkflow extends EagerModel { /***/ public static final TaggedGroup MODEL_TAGS = new TaggedGroup( "chain:workflow" ) @@ -30,7 +30,7 @@ public class Latest extends EagerModel { /** * @param index The source of the index-get flow */ - public Latest( Index index ) { + public LatestFromWorkflow( Index index ) { super( MODEL_TAGS ); Chain chain = new Chain( "workflow" ); diff --git a/model/src/main/java/dev/flowty/bowlby/model/msg/HttpMessage.java b/model/src/main/java/dev/flowty/bowlby/model/msg/HttpMessage.java index 4eaee9b..fd12cde 100644 --- a/model/src/main/java/dev/flowty/bowlby/model/msg/HttpMessage.java +++ b/model/src/main/java/dev/flowty/bowlby/model/msg/HttpMessage.java @@ -9,6 +9,7 @@ import com.mastercard.test.flow.msg.http.HttpMsg; import com.mastercard.test.flow.msg.http.HttpReq; import com.mastercard.test.flow.msg.http.HttpRes; +import com.mastercard.test.flow.msg.txt.Text; import com.mastercard.test.flow.msg.xml.XML; import dev.flowty.bowlby.model.BowlbySystem.Unpredictables; @@ -149,6 +150,25 @@ public static Message directoryListing( String... files ) { return res; } + /** + * Builds an artifact0content text response + * + * @param body The expected file content + * @return The 200 response message + */ + public static Message textResponse( String body ) { + return new HttpRes() + .set( HttpMsg.VERSION, "HTTP/1.1" ) + .set( HttpRes.STATUS, 200 ) + .set( HttpMsg.header( "cache-control" ), "max-age=31536000, immutable" ) + .set( HttpMsg.header( "content-type" ), "text/plain" ) + .set( HttpMsg.BODY, new Text( body ) ) + .masking( Unpredictables.BORING, m -> m + .delete( Stream.of( + "content-length", "date" ) + .map( HttpMsg::header ) ) ); + } + /** * Builds bowlby's icon response. Note that we're not bothering to model the * icon content diff --git a/model/src/main/java/dev/flowty/bowlby/model/msg/WebMessage.java b/model/src/main/java/dev/flowty/bowlby/model/msg/WebMessage.java index 125aad7..1b387b5 100644 --- a/model/src/main/java/dev/flowty/bowlby/model/msg/WebMessage.java +++ b/model/src/main/java/dev/flowty/bowlby/model/msg/WebMessage.java @@ -93,6 +93,15 @@ public static Message summarise() { "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d+Z", "_masked_" ) ) ); } + /** + * @return extracts the page text + */ + public static Message text() { + return new WebSequence() + .operation( "dump text", ( driver, params ) -> params + .put( "text", driver.findElement( By.tagName( "html" ) ).getText() ) ); + } + private static String summarise( WebDriver driver, String tag ) { return driver.findElements( By.tagName( tag ) ).stream() .map( e -> summarise( driver, e ) ) diff --git a/test/src/test/java/dev/flowty/bowlby/it/ApiIT.java b/test/src/test/java/dev/flowty/bowlby/it/ApiIT.java index 249aa92..98f6254 100644 --- a/test/src/test/java/dev/flowty/bowlby/it/ApiIT.java +++ b/test/src/test/java/dev/flowty/bowlby/it/ApiIT.java @@ -24,7 +24,6 @@ import dev.flowty.bowlby.model.BowlbySystem; import dev.flowty.bowlby.test.HttpFlow; -import dev.flowty.bowlby.test.TestLog; /** * Exercises github in isolation @@ -54,9 +53,7 @@ Stream tests() { return new Flocessor( "github", BowlbySystem.MODEL ) .system( State.FUL, GITHUB ) .masking( BORING, RNG ) - .logs( TestLog.TAIL ) - // reports will leak the token! - .reporting( Reporting.NEVER, "github" ) + .reporting( Reporting.ALWAYS, "github" ) .behaviour( asrt -> { HttpReq request = (HttpReq) asrt.expected().request().child();