test(ext-web-bundler): migrate integration test to flash-testing
Two frontend layouts, each laid out on disk inside its own application's configure(). The harness runs that lazily at first access, so @TempDir is populated by then and the server for whichever test is not running never boots. Replaces three blocks of HttpRequest.newBuilder(URI.create(...)) per test with single-line assertions. 98 to 60 code lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c02459dd7c
commit
7785712efe
+53
-94
@@ -1,122 +1,81 @@
|
||||
package dev.relism.flash.ext.webbundler;
|
||||
|
||||
import dev.relism.flash.extension.FlashApp;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import dev.relism.flash.testing.FlashTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.net.ServerSocket;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Two frontend layouts served by a real server. Each is laid out on disk inside the
|
||||
* application's own configure(), which the harness runs lazily at first access — by then
|
||||
* {@link TempDir} is populated, and a server whose test never runs is never booted.
|
||||
*/
|
||||
class WebBundlerExtensionIntegrationTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
static Path tempDir;
|
||||
|
||||
private FlashApp app;
|
||||
@RegisterExtension
|
||||
static FlashTest managed = FlashTest.of(app -> {
|
||||
Path webRoot = write(tempDir.resolve("web").resolve("dist"),
|
||||
"index.html", "<html>spa</html>",
|
||||
"app.js", "console.log('ok');").getParent();
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (app != null) app.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void prodMode_servesAssetsAndFallback_withoutBreakingBackendRoutes() throws Exception {
|
||||
Path webRoot = tempDir.resolve("web");
|
||||
Path dist = webRoot.resolve("dist");
|
||||
Files.createDirectories(dist);
|
||||
Files.writeString(dist.resolve("index.html"), "<html>spa</html>");
|
||||
Files.writeString(dist.resolve("app.js"), "console.log('ok');");
|
||||
|
||||
int port;
|
||||
try (ServerSocket s = new ServerSocket(0)) {
|
||||
port = s.getLocalPort();
|
||||
}
|
||||
|
||||
WebBundlerConfig config = WebBundlerConfig.builder()
|
||||
app.install(new WebBundlerExtension(WebBundlerConfig.builder()
|
||||
.runtimeMode(RuntimeMode.PROD)
|
||||
.operationMode(OperationMode.MANAGED)
|
||||
.webRoot(webRoot)
|
||||
.assetsFromFilesystem(Path.of("dist"))
|
||||
.basePath("/app")
|
||||
.build();
|
||||
|
||||
app = FlashApp.create(port);
|
||||
app.install(new WebBundlerExtension(config));
|
||||
.build()));
|
||||
app.get("/api/ping", (req, res) -> "pong");
|
||||
app.start();
|
||||
});
|
||||
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpResponse<String> backend = client.send(
|
||||
HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + port + "/api/ping")).GET().build(),
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
assertEquals(200, backend.statusCode());
|
||||
assertEquals("pong", backend.body());
|
||||
// PROD is deterministic in a test JVM (Flash.DEV depends on env/system-property detection
|
||||
// that can't be forced per-test); STATIC's actual guarantee — that it never orchestrates,
|
||||
// in DEV or PROD — is enforced structurally by the same requiresOrchestration() gate in
|
||||
// both WebBundlerExtension.provide() and .routes(), not by this test.
|
||||
@RegisterExtension
|
||||
static FlashTest staticFrontend = FlashTest.of(app -> {
|
||||
Path webRoot = write(tempDir.resolve("public"),
|
||||
"index.html", "<html>static</html>",
|
||||
"style.css", "body{color:red}");
|
||||
|
||||
HttpResponse<String> asset = client.send(
|
||||
HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + port + "/app/app.js")).GET().build(),
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
assertEquals(200, asset.statusCode());
|
||||
assertTrue(asset.body().contains("console.log"));
|
||||
|
||||
HttpResponse<String> fallback = client.send(
|
||||
HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + port + "/app/some/client/route")).GET().build(),
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
assertEquals(200, fallback.statusCode());
|
||||
assertTrue(fallback.body().contains("spa"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticFrontend_servesAssetsWithoutOrchestration() throws Exception {
|
||||
Path webRoot = tempDir.resolve("public");
|
||||
Files.createDirectories(webRoot);
|
||||
Files.writeString(webRoot.resolve("index.html"), "<html>static</html>");
|
||||
Files.writeString(webRoot.resolve("style.css"), "body{color:red}");
|
||||
|
||||
int port;
|
||||
try (ServerSocket s = new ServerSocket(0)) {
|
||||
port = s.getLocalPort();
|
||||
}
|
||||
|
||||
// PROD is deterministic in a test JVM (Flash.DEV depends on env/system-property detection
|
||||
// that can't be forced per-test); STATIC's actual guarantee — that it never orchestrates,
|
||||
// in DEV or PROD — is enforced structurally by the same requiresOrchestration() gate in
|
||||
// both WebBundlerExtension.provide() and .routes(), not by this test.
|
||||
WebBundlerConfig config = WebBundlerConfig.builder()
|
||||
app.install(new WebBundlerExtension(WebBundlerConfig.builder()
|
||||
.runtimeMode(RuntimeMode.PROD)
|
||||
.frontendType(FrontendType.STATIC)
|
||||
.webRoot(webRoot)
|
||||
.build();
|
||||
|
||||
app = FlashApp.create(port);
|
||||
app.install(new WebBundlerExtension(config));
|
||||
.build()));
|
||||
app.get("/api/ping", (req, res) -> "pong");
|
||||
app.start();
|
||||
});
|
||||
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpResponse<String> backend = client.send(
|
||||
HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + port + "/api/ping")).GET().build(),
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
assertEquals(200, backend.statusCode());
|
||||
assertEquals("pong", backend.body());
|
||||
|
||||
HttpResponse<String> asset = client.send(
|
||||
HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + port + "/style.css")).GET().build(),
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
assertEquals(200, asset.statusCode());
|
||||
assertTrue(asset.body().contains("color:red"));
|
||||
@Test
|
||||
void prodMode_servesAssetsAndFallback_withoutBreakingBackendRoutes() {
|
||||
managed.get("/api/ping").expectStatus(200).expectBody("pong");
|
||||
managed.get("/app/app.js").expectStatus(200).expectBodyContains("console.log");
|
||||
managed.get("/app/some/client/route").expectStatus(200).expectBodyContains("spa");
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticFrontend_servesAssetsWithoutOrchestration() {
|
||||
staticFrontend.get("/api/ping").expectStatus(200).expectBody("pong");
|
||||
staticFrontend.get("/style.css").expectStatus(200).expectBodyContains("color:red");
|
||||
}
|
||||
|
||||
/** Creates {@code directory} and writes the given name/content pairs into it. */
|
||||
private static Path write(Path directory, String... nameThenContent) {
|
||||
try {
|
||||
Files.createDirectories(directory);
|
||||
for (int i = 0; i < nameThenContent.length; i += 2)
|
||||
Files.writeString(directory.resolve(nameThenContent[i]), nameThenContent[i + 1]);
|
||||
return directory;
|
||||
} catch (IOException failure) {
|
||||
throw new UncheckedIOException(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user