Merge remote-tracking branch 'origin/master'
# Conflicts: # .idea/workspace.xml # flash-extensions/flash-ext-view-jte/jte-classes/gg/jte/generated/ondemand/pages/JtehomeGenerated.class # flash-extensions/flash-ext-view-jte/src/main/java/dev/relism/flash/ext/view/jte/JteExtension.java
This commit is contained in:
+50
@@ -0,0 +1,50 @@
|
||||
package dev.relism.ext.view.jte;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class JteStaticServingTest {
|
||||
|
||||
@Test
|
||||
void etag_is_stable_for_same_payload_and_changes_for_different() {
|
||||
byte[] a = "hello".getBytes();
|
||||
byte[] b = "hello".getBytes();
|
||||
byte[] c = "hello!".getBytes();
|
||||
|
||||
String etagA = JteStaticServing.etagFor(a);
|
||||
String etagB = JteStaticServing.etagFor(b);
|
||||
String etagC = JteStaticServing.etagFor(c);
|
||||
|
||||
assertEquals(etagA, etagB);
|
||||
assertNotEquals(etagA, etagC);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mime_resolution_supports_common_extensions_and_defaults() {
|
||||
assertEquals("text/css", JteStaticServing.mimeFor("/sample.css"));
|
||||
assertEquals("text/javascript", JteStaticServing.mimeFor("/sample.js"));
|
||||
assertEquals("application/wasm", JteStaticServing.mimeFor("/sample.wasm"));
|
||||
assertEquals("application/octet-stream", JteStaticServing.mimeFor("/sample.unknown"));
|
||||
assertEquals("application/octet-stream", JteStaticServing.mimeFor("/sample"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void versioned_path_detection_by_filename_hash() {
|
||||
assertTrue(JteStaticServing.versionedPath("/app.4f3a2c1b.js"));
|
||||
assertTrue(JteStaticServing.versionedPath("/app.abcdefabcdef.js"));
|
||||
assertFalse(JteStaticServing.versionedPath("/app.js"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void range_parsing_supports_basic_cases() {
|
||||
assertEquals(10, JteStaticServing.rangeLength("bytes=0-9", 100));
|
||||
assertEquals(100, JteStaticServing.rangeLength("bytes=0-", 100));
|
||||
assertEquals(10, JteStaticServing.rangeLength("bytes=-10", 100));
|
||||
assertEquals(-1, JteStaticServing.rangeLength("bytes=100-120", 100));
|
||||
assertEquals(-1, JteStaticServing.rangeLength("invalid", 100));
|
||||
}
|
||||
}
|
||||
+106
@@ -8,6 +8,11 @@ import dev.relism.flash.routing.GET;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@@ -65,6 +70,69 @@ class JteExtensionTest {
|
||||
assertTrue(ex.getMessage().contains("must declare @Template"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fluent_static_settings_are_applied() {
|
||||
JteExtension ext = new JteExtension()
|
||||
.templateRoot("templates")
|
||||
.serveStatics(false)
|
||||
.staticPrefix("assets")
|
||||
.withStaticCors()
|
||||
.staticCors(cfg -> cfg
|
||||
.staticCorsAllowOrigin("https://cdn.example.com"));
|
||||
|
||||
JteSettings settings = readSettings(ext);
|
||||
assertEquals("/templates", settings.templateRoot());
|
||||
assertFalse(settings.serveStatics());
|
||||
assertEquals("/assets", settings.staticPrefix());
|
||||
assertTrue(settings.staticCorsEnabled());
|
||||
assertEquals("https://cdn.example.com", settings.staticCorsAllowOrigin());
|
||||
}
|
||||
|
||||
@Test
|
||||
void routes_register_static_wildcard_when_enabled() {
|
||||
JteExtension ext = new JteExtension(cfg -> cfg.staticPrefix("/assets"));
|
||||
TestRegistrar app = new TestRegistrar();
|
||||
ext.routes(app, new dev.relism.extension.FlashContext());
|
||||
assertTrue(app.routes.containsKey("GET /assets/**"));
|
||||
assertTrue(app.routes.containsKey("HEAD /assets/**"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void routes_do_not_register_static_when_disabled() {
|
||||
JteExtension ext = new JteExtension().serveStatics(false);
|
||||
TestRegistrar app = new TestRegistrar();
|
||||
ext.routes(app, new dev.relism.extension.FlashContext());
|
||||
assertTrue(app.routes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void static_handler_serves_existing_asset() throws Exception {
|
||||
JteExtension ext = new JteExtension(cfg -> cfg
|
||||
.staticPrefix("/assets")
|
||||
.largeFileThresholdBytes(1));
|
||||
TestRegistrar app = new TestRegistrar();
|
||||
ext.routes(app, new dev.relism.extension.FlashContext());
|
||||
|
||||
Request req = request("/assets/sample.css", null, null, null);
|
||||
Response res = new Response(200, dev.relism.http.ContentType.TEXT_PLAIN);
|
||||
Object out = app.routes.get("GET /assets/**").handle(req, res);
|
||||
|
||||
assertEquals(null, out);
|
||||
assertEquals("text/css", new String(res.getContentType(), StandardCharsets.UTF_8));
|
||||
assertTrue(res.isStreaming() || res.getBody() != null);
|
||||
assertEquals(200, res.getStatusCode());
|
||||
}
|
||||
|
||||
private static JteSettings readSettings(JteExtension ext) {
|
||||
try {
|
||||
var f = JteExtension.class.getDeclaredField("settings");
|
||||
f.setAccessible(true);
|
||||
return (JteSettings) f.get(ext);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void invokeValidate(JteExtension ext, Class<? extends RequestHandler> type) throws Exception {
|
||||
Method m = JteExtension.class.getDeclaredMethod("validateHandlerClass", Class.class);
|
||||
m.setAccessible(true);
|
||||
@@ -77,4 +145,42 @@ class JteExtensionTest {
|
||||
throw new RuntimeException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private static Request request(String path, String query, String acceptEncoding, String origin) {
|
||||
dev.relism.models.HeaderMap headers = new dev.relism.models.HeaderMap();
|
||||
String raw = "Host: localhost\r\n";
|
||||
if (acceptEncoding != null) raw += "Accept-Encoding: " + acceptEncoding + "\r\n";
|
||||
if (origin != null) raw += "Origin: " + origin + "\r\n";
|
||||
byte[] bytes = raw.getBytes(StandardCharsets.UTF_8);
|
||||
headers.reset(bytes, 0, bytes.length);
|
||||
|
||||
dev.relism.models.RequestLine line = new dev.relism.models.RequestLine(
|
||||
dev.relism.http.HttpMethod.GET,
|
||||
new dev.relism.routing.routers.fastpathrouter.FastPathViews.StringByteView(path),
|
||||
query == null ? null : new dev.relism.routing.routers.fastpathrouter.FastPathViews.StringByteView(query),
|
||||
new dev.relism.routing.routers.fastpathrouter.FastPathViews.StringByteView("HTTP/1.1"),
|
||||
headers
|
||||
);
|
||||
return new Request(line, new byte[0]);
|
||||
}
|
||||
|
||||
private static final class TestRegistrar extends dev.relism.extension.FlashRegistrar<TestRegistrar> {
|
||||
private final Map<String, RequestHandler> routes = new HashMap<>();
|
||||
private final List<dev.relism.routing.Middleware> mws = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public dev.relism.extension.FlashContext ctx() {
|
||||
return new dev.relism.extension.FlashContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addRoute(dev.relism.http.HttpMethod method, String path, RequestHandler handler, List<dev.relism.routing.Middleware> mw) {
|
||||
routes.put(method.name() + " " + path, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addMiddleware(dev.relism.routing.Middleware mw) {
|
||||
mws.add(mw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -20,6 +20,14 @@ class JteSettingsTest {
|
||||
assertFalse(settings.binaryStaticContent());
|
||||
assertEquals(Path.of("jte-classes"), settings.dynamicClassesPath());
|
||||
assertEquals(Path.of("jte-classes"), settings.precompiledClassesPath());
|
||||
assertTrue(settings.serveStatics());
|
||||
assertEquals("/static", settings.staticPrefix());
|
||||
assertEquals(64L * 1024L, settings.largeFileThresholdBytes());
|
||||
assertFalse(settings.staticCorsEnabled());
|
||||
assertEquals("*", settings.staticCorsAllowOrigin());
|
||||
assertEquals("GET,HEAD,OPTIONS", settings.staticCorsAllowMethods());
|
||||
assertEquals("*", settings.staticCorsAllowHeaders());
|
||||
assertEquals(3600, settings.staticCorsMaxAge());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -32,6 +40,14 @@ class JteSettingsTest {
|
||||
.binaryStaticContent(true)
|
||||
.dynamicClassesPath(Path.of("var", "jte-dev"))
|
||||
.precompiledClassesPath(Path.of("var", "jte-prod"))
|
||||
.serveStatics(false)
|
||||
.staticPrefix("assets")
|
||||
.largeFileThresholdBytes(1024)
|
||||
.enableStaticCors(true)
|
||||
.staticCorsAllowOrigin("https://cdn.example.com")
|
||||
.staticCorsAllowMethods("GET,HEAD")
|
||||
.staticCorsAllowHeaders("Origin,Content-Type")
|
||||
.staticCorsMaxAge(120)
|
||||
.build();
|
||||
|
||||
assertEquals("/src/main/jte", settings.templateRoot());
|
||||
@@ -41,6 +57,14 @@ class JteSettingsTest {
|
||||
assertTrue(settings.binaryStaticContent());
|
||||
assertEquals(Path.of("var", "jte-dev"), settings.dynamicClassesPath());
|
||||
assertEquals(Path.of("var", "jte-prod"), settings.precompiledClassesPath());
|
||||
assertFalse(settings.serveStatics());
|
||||
assertEquals("/assets", settings.staticPrefix());
|
||||
assertEquals(1024, settings.largeFileThresholdBytes());
|
||||
assertTrue(settings.staticCorsEnabled());
|
||||
assertEquals("https://cdn.example.com", settings.staticCorsAllowOrigin());
|
||||
assertEquals("GET,HEAD", settings.staticCorsAllowMethods());
|
||||
assertEquals("Origin,Content-Type", settings.staticCorsAllowHeaders());
|
||||
assertEquals(120, settings.staticCorsMaxAge());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,10 +84,22 @@ class JteSettingsTest {
|
||||
assertThrows(IllegalArgumentException.class, () -> JteSettings.builder().templateRoot(" "));
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticPrefix_normalizes_andRejectsBlank() {
|
||||
assertEquals("/static", JteSettings.builder().staticPrefix("static/").build().staticPrefix());
|
||||
assertEquals("/assets", JteSettings.builder().staticPrefix("/assets/").build().staticPrefix());
|
||||
assertThrows(IllegalArgumentException.class, () -> JteSettings.builder().staticPrefix(" "));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nulls_areRejectedForRequiredObjects() {
|
||||
assertThrows(NullPointerException.class, () -> JteSettings.builder().contentType(null));
|
||||
assertThrows(NullPointerException.class, () -> JteSettings.builder().dynamicClassesPath(null));
|
||||
assertThrows(NullPointerException.class, () -> JteSettings.builder().precompiledClassesPath(null));
|
||||
assertThrows(IllegalArgumentException.class, () -> JteSettings.builder().largeFileThresholdBytes(0));
|
||||
assertThrows(IllegalArgumentException.class, () -> JteSettings.builder().staticCorsAllowOrigin(" "));
|
||||
assertThrows(IllegalArgumentException.class, () -> JteSettings.builder().staticCorsAllowMethods(" "));
|
||||
assertThrows(IllegalArgumentException.class, () -> JteSettings.builder().staticCorsAllowHeaders(" "));
|
||||
assertThrows(IllegalArgumentException.class, () -> JteSettings.builder().staticCorsMaxAge(-1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
body { color: #111; }
|
||||
@@ -0,0 +1 @@
|
||||
gzipped-sample
|
||||
Reference in New Issue
Block a user