Skip to content

Commit

Permalink
first complex flow complete
Browse files Browse the repository at this point in the history
  • Loading branch information
therealryan committed Aug 7, 2024
1 parent ca68ec9 commit 2cb1b6d
Show file tree
Hide file tree
Showing 19 changed files with 825 additions and 82 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ github actions artifact proxy
# TODO

1. Tests
1. better browser control in testing - headless default, stepping, etc
1. GUI for local use
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
Expand Down Expand Up @@ -51,7 +53,9 @@ public class GithubApiClient {

private final String apiHost;
private final String authToken;
private final HttpClient http = HttpClient.newBuilder().build();
private final HttpClient http = HttpClient.newBuilder()
.version( Version.HTTP_1_1 )
.build();

/**
* Holds the last time that we made an api call.
Expand Down Expand Up @@ -264,7 +268,12 @@ private synchronized <T> HttpResponse<T> send( HttpRequest request, BodyHandler<
LOG.debug( "Sending request {}", request );
HttpResponse<T> response = http.send( request, handler );
if( LOG.isDebugEnabled() ) {
LOG.debug( "Got response {}\n{}", response, Message.toJson( response.body() ) );
LOG.debug( "Got response\n{}\n{}\n{}",
response,
response.headers().map().entrySet().stream()
.map( e -> e.getKey() + ": " + e.getValue().stream().collect( joining( ", " ) ) )
.collect( joining( "\n" ) ),
Message.toJson( response.body() ) );
}

lastCall = Instant.now();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Stream;

import org.slf4j.Logger;
Expand All @@ -25,8 +26,8 @@
import dev.flowty.bowlby.app.github.Entity.Repository;
import dev.flowty.bowlby.app.github.Entity.Run;
import dev.flowty.bowlby.app.github.Entity.Workflow;
import dev.flowty.bowlby.app.xml.Html;
import dev.flowty.bowlby.app.github.GithubApiClient;
import dev.flowty.bowlby.app.xml.Html;

/**
* Handles requests to:
Expand Down Expand Up @@ -97,7 +98,7 @@ public void handle( HttpExchange exchange ) throws IOException {

if( path.isEmpty() ) {
// the path has no indication of which artifact we're interested in
showArtifactLinks( exchange, 200, workflow, latest );
showArtifactLinks( exchange, 300, workflow, latest );
return;
}

Expand All @@ -113,6 +114,9 @@ public void handle( HttpExchange exchange ) throws IOException {
return;
}

Duration cacheLife = Duration.between( Instant.now(), latest.expiry() );
exchange.getResponseHeaders().add( "cache-control",
"public; immutable; max-age=" + cacheLife.getSeconds() );
ServeUtil.redirect( exchange, String.format(
"/artifacts/%s/%s/%s/%s",
workflow.repo().owner(), workflow.repo().repo(), selected.artifact().id(),
Expand Down Expand Up @@ -163,14 +167,16 @@ private LatestArtifact getLatest( Workflow workflow ) {

private static void showArtifactLinks( HttpExchange exchange, int status, Workflow workflow,
LatestArtifact latest ) throws IOException {

Function<NamedArtifact, String> artifactPath = na -> String.format( "/latest/%s/%s/%s/%s",
workflow.repo().owner(),
workflow.repo().repo(),
workflow.name(),
na.name() );
latest.artifacts().forEach( a -> exchange.getResponseHeaders()
.add( "link", "<" + artifactPath.apply( a ) + ">; rel=alternate" ) );
BiConsumer<Html, NamedArtifact> artifactItem = ( html, artifact ) -> {
html.li( i -> i.a(
String.format( "/latest/%s/%s/%s/%s",
workflow.repo().owner(),
workflow.repo().repo(),
workflow.name(),
artifact.name() ),
artifact.name() ) );
html.li( i -> i.a( artifactPath.apply( artifact ), artifact.name() ) );
};
ServeUtil.respond( exchange, status, new Html()
.head( h -> h
Expand All @@ -184,11 +190,8 @@ private static void showArtifactLinks( HttpExchange exchange, int status, Workfl
.conditional( c -> c
.p( "These stable links will redirect to the latest artifacts for the ",
workflow.name(), " workflow on the default branch. ",
"Feel free to append path components to address files within the artifacts" )
.ul( u -> u
.repeat( artifactItem ).over( latest.artifacts() ) )
.p( "If you use these links after ", latest.expiry,
", then they might redirect to a newer artifact" ) )
"Feel free to append path components to address files within the artifacts." )
.ul( u -> u.repeat( artifactItem ).over( latest.artifacts() ) ) )
.on( !latest.artifacts().isEmpty() ) )
.toString() );
}
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/java/dev/flowty/bowlby/app/srv/ServeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ public static void redirect( HttpExchange exchange, String destination ) throws
LOG.debug( "redirecting to {}", destination );
exchange.getResponseHeaders()
.add( "location", destination );
exchange.sendResponseHeaders( 303, 0 );
exchange.getResponseBody().close();
exchange.sendResponseHeaders( 303, -1 );
}

/**
Expand Down
70 changes: 27 additions & 43 deletions app/src/test/java/dev/flowty/bowlby/app/MainTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@
import static dev.flowfty.bowlby.model.BowlbySystem.Actors.BOWLBY;
import static dev.flowfty.bowlby.model.BowlbySystem.Unpredictables.BORING;
import static dev.flowfty.bowlby.model.BowlbySystem.Unpredictables.RNG;
import static java.util.stream.Collectors.joining;
import static org.junit.jupiter.api.Assertions.fail;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.stream.Stream;
Expand All @@ -24,14 +20,14 @@
import com.mastercard.test.flow.assrt.AbstractFlocessor.State;
import com.mastercard.test.flow.assrt.Reporting;
import com.mastercard.test.flow.assrt.junit5.Flocessor;
import com.mastercard.test.flow.msg.ExposedMasking;
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 dev.flowfty.bowlby.model.BowlbySystem;
import dev.flowfty.bowlby.model.BowlbySystem.Actors;
import dev.flowty.bowlby.app.cfg.Parameters;
import dev.flowty.bowlby.test.HttpFlow;
import dev.flowty.bowlby.test.MockHost;
import dev.flowty.bowlby.test.TestLog;

/**
* Exercises bowlby in isolation
Expand All @@ -40,15 +36,25 @@
class MainTest {

private static Main app;
private static URI target;
private static MockHost github;
private static final HttpClient HTTP = HttpClient.newBuilder().build();

/**
* Starts the bowlby instance
* Starts the bowlby instance and github mock
*
* @throws URISyntaxException if we fail to build the target uri
*/
@BeforeAll
static void start() {
app = new Main( new Parameters( "-p", "0" ) );
static void start() throws URISyntaxException {
github = new MockHost( Actors.GITHUB );
github.start();
app = new Main( new Parameters(
"-p", "0",
"-g", "http:/" + github.address(),
"-t", "_auth_token_" ) );
app.start();
target = new URI( "http:/" + app.address() );
}

/**
Expand All @@ -59,15 +65,20 @@ Stream<DynamicNode> tests() {
return new Flocessor( "isolation", BowlbySystem.MODEL )
.system( State.FUL, BOWLBY )
.masking( BORING, RNG )
.logs( TestLog.TAIL )
.reporting( Reporting.FAILURES )
.behaviour( asrt -> {
HttpReq req = (HttpReq) asrt.expected().request().child();
HttpReq request = (HttpReq) asrt.expected().request().child();
try {
github.seedResponses( asrt );

HttpResponse<String> response = HTTP.send(
map( req ),
HttpFlow.sendable( target, request ),
BodyHandlers.ofString() );

asrt.actual()
.response( content( response ) );
.response( HttpFlow.assertable( response ) );
asrt.assertConsequests( github.captured() );
}
catch( Exception e ) {
fail( e );
Expand All @@ -76,40 +87,13 @@ Stream<DynamicNode> tests() {
.tests();
}

private static HttpRequest map( HttpReq req ) {
try {
URI uri = new URI( String.format( "http:/%s%s", app.address(), req.path() ) );
Builder builder = HttpRequest.newBuilder()
.uri( uri )
.method( req.method(),
BodyPublishers.ofByteArray( req.body()
.map( ExposedMasking::content )
.orElse( new byte[0] ) ) );

req.headers().forEach( builder::header );
return builder.build();
}
catch( URISyntaxException e ) {
throw new IllegalStateException( e );
}
}

private static byte[] content( HttpResponse<String> response ) {
HttpRes res = new HttpRes();
res.set( HttpRes.STATUS, response.statusCode() );
response.headers().map().forEach( ( n, v ) -> res.set(
HttpMsg.header( n ),
v.stream().collect( joining( "," ) ) ) );
res.set( HttpMsg.BODY, new Text( response.body() ) );
return res.content();
}

/**
* Stops the bowlby instance
* Stops the bowlby instance and the github mock
*/
@AfterAll
static void stop() {
app.stop();
github.stop();
}

}
62 changes: 62 additions & 0 deletions assert/src/main/java/dev/flowty/bowlby/test/HttpFlow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package dev.flowty.bowlby.test;

import static java.util.stream.Collectors.joining;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;

import com.mastercard.test.flow.msg.ExposedMasking;
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;

/**
* A bridge between the flow data model and {@link HttpClient}.
*/
public class HttpFlow {

/**
* Builds a request from flow data
*
* @param host The target of the request
* @param req The request content
* @return A sendable request
*/
public static HttpRequest sendable( URI host, HttpReq req ) {
URI uri = host.resolve( req.path() );
Builder builder = HttpRequest.newBuilder()
.uri( uri )
.method( req.method(),
BodyPublishers.ofByteArray( req.body()
.map( ExposedMasking::content )
.orElse( new byte[0] ) ) );

req.headers().forEach( builder::header );
return builder.build();
}

/**
* Converts a response into assertable content
*
* @param response The response
* @return the message content to supply to flow
*/
public static byte[] assertable( HttpResponse<String> response ) {
HttpRes res = new HttpRes();
if( response.version() == Version.HTTP_1_1 ) {
res.set( HttpMsg.VERSION, "HTTP/1.1" );
}
res.set( HttpRes.STATUS, response.statusCode() );
response.headers().map().forEach( ( n, v ) -> res.set(
HttpMsg.header( n ),
v.stream().collect( joining( "," ) ) ) );
res.set( HttpMsg.BODY, new Text( response.body() ) );
return res.content();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.mastercard.test.flow.model.LazyModel;

import dev.flowfty.bowlby.model.flow.Index;
import dev.flowfty.bowlby.model.flow.Latest;

/**
* Models the expected behaviour of the system
Expand Down Expand Up @@ -36,7 +37,7 @@ public enum Actors implements Actor {
/**
* The mysterious download host where the artifacts are supplied from
*/
ARTIFACT_HOST;
ARTIFACTS;
}

/**
Expand All @@ -58,6 +59,7 @@ public enum Unpredictables implements Unpredictable {
*/
public static final Model MODEL = new LazyModel( "Bowlby" )
.with( Index.class )
.with( Latest.class )

;
}
Loading

0 comments on commit 2cb1b6d

Please sign in to comment.