Skip to content

Commit 19ab92e

Browse files
committed
Restore ScriptTemplateViewTests
Restore both WebMVC and WebFlux variants that were deleted by mistake in commit 4db2f8e. This commit also removes the empty resource loader path, as it is not needed for the main WEB-INF/ use case that is typically configured explicitly by the user, and not needed to pass the restored tests. Closes gh-36456
1 parent 04313f0 commit 19ab92e

File tree

4 files changed

+602
-6
lines changed

4 files changed

+602
-6
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/script/ScriptTemplateView.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,13 @@ public void setRenderFunction(String functionName) {
158158
*/
159159
public void setResourceLoaderPath(String resourceLoaderPath) {
160160
String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
161-
this.resourceLoaderPaths = new String[paths.length + 1];
162-
this.resourceLoaderPaths[0] = "";
161+
this.resourceLoaderPaths = new String[paths.length];
163162
for (int i = 0; i < paths.length; i++) {
164163
String path = paths[i];
165164
if (!path.endsWith("/") && !path.endsWith(":")) {
166165
path = path + "/";
167166
}
168-
this.resourceLoaderPaths[i + 1] = path;
167+
this.resourceLoaderPaths[i] = path;
169168
}
170169
}
171170

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.result.view.script;
18+
19+
import java.nio.charset.StandardCharsets;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.Locale;
23+
import java.util.concurrent.ExecutorService;
24+
import java.util.concurrent.Executors;
25+
import java.util.concurrent.Future;
26+
27+
import javax.script.Invocable;
28+
import javax.script.ScriptEngine;
29+
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
33+
import org.springframework.beans.DirectFieldAccessor;
34+
import org.springframework.context.ApplicationContextException;
35+
import org.springframework.context.support.StaticApplicationContext;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
39+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
40+
import static org.assertj.core.api.InstanceOfAssertFactories.BOOLEAN;
41+
import static org.mockito.BDDMockito.given;
42+
import static org.mockito.Mockito.mock;
43+
44+
/**
45+
* Unit tests for {@link ScriptTemplateView}.
46+
*
47+
* @author Sebastien Deleuze
48+
*/
49+
public class ScriptTemplateViewTests {
50+
51+
private ScriptTemplateView view;
52+
53+
private ScriptTemplateConfigurer configurer;
54+
55+
private StaticApplicationContext context;
56+
57+
58+
@BeforeEach
59+
public void setup() {
60+
this.configurer = new ScriptTemplateConfigurer();
61+
this.context = new StaticApplicationContext();
62+
this.context.getBeanFactory().registerSingleton("scriptTemplateConfigurer", this.configurer);
63+
this.view = new ScriptTemplateView();
64+
}
65+
66+
67+
@Test
68+
public void missingTemplate() throws Exception {
69+
this.context.refresh();
70+
this.view.setResourceLoaderPath("classpath:org/springframework/web/reactive/result/view/script/");
71+
this.view.setUrl("missing.txt");
72+
this.view.setEngine(mock(InvocableScriptEngine.class));
73+
this.configurer.setRenderFunction("render");
74+
this.view.setApplicationContext(this.context);
75+
assertThat(this.view.checkResourceExists(Locale.ENGLISH)).isFalse();
76+
}
77+
78+
@Test
79+
public void missingScriptTemplateConfig() throws Exception {
80+
assertThatExceptionOfType(ApplicationContextException.class).isThrownBy(() ->
81+
this.view.setApplicationContext(new StaticApplicationContext()))
82+
.withMessageContaining("ScriptTemplateConfig");
83+
}
84+
85+
@Test
86+
public void detectScriptTemplateConfigWithEngine() {
87+
InvocableScriptEngine engine = mock(InvocableScriptEngine.class);
88+
this.configurer.setEngine(engine);
89+
this.configurer.setRenderObject("Template");
90+
this.configurer.setRenderFunction("render");
91+
this.configurer.setCharset(StandardCharsets.ISO_8859_1);
92+
this.configurer.setSharedEngine(true);
93+
94+
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
95+
this.view.setApplicationContext(this.context);
96+
assertThat(accessor.getPropertyValue("engine")).isEqualTo(engine);
97+
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
98+
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
99+
assertThat(accessor.getPropertyValue("defaultCharset")).isEqualTo(StandardCharsets.ISO_8859_1);
100+
assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isTrue();
101+
}
102+
103+
@Test
104+
public void detectScriptTemplateConfigWithEngineName() {
105+
this.configurer.setEngineName("jython");
106+
this.configurer.setRenderObject("Template");
107+
this.configurer.setRenderFunction("render");
108+
109+
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
110+
this.view.setApplicationContext(this.context);
111+
assertThat(accessor.getPropertyValue("engineName")).isEqualTo("jython");
112+
assertThat(accessor.getPropertyValue("engine")).isNotNull();
113+
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
114+
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
115+
assertThat(accessor.getPropertyValue("defaultCharset")).isEqualTo(StandardCharsets.UTF_8);
116+
}
117+
118+
@Test
119+
public void customEngineAndRenderFunction() throws Exception {
120+
ScriptEngine engine = mock(InvocableScriptEngine.class);
121+
given(engine.get("key")).willReturn("value");
122+
this.view.setEngine(engine);
123+
this.view.setRenderFunction("render");
124+
this.view.setApplicationContext(this.context);
125+
engine = this.view.getEngine();
126+
assertThat(engine).isNotNull();
127+
assertThat(engine.get("key")).isEqualTo("value");
128+
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
129+
assertThat(accessor.getPropertyValue("renderObject")).isNull();
130+
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
131+
assertThat(accessor.getPropertyValue("defaultCharset")).isEqualTo(StandardCharsets.UTF_8);
132+
}
133+
134+
@Test
135+
public void nonSharedEngine() throws Exception {
136+
int iterations = 20;
137+
this.view.setEngineName("jython");
138+
this.view.setRenderFunction("render");
139+
this.view.setSharedEngine(false);
140+
this.view.setApplicationContext(this.context);
141+
ExecutorService executor = Executors.newFixedThreadPool(4);
142+
List<Future<Boolean>> results = new ArrayList<>();
143+
for (int i = 0; i < iterations; i++) {
144+
results.add(executor.submit(() -> view.getEngine() != null));
145+
}
146+
assertThat(results.size()).isEqualTo(iterations);
147+
for (int i = 0; i < iterations; i++) {
148+
assertThat((boolean) results.get(i).get()).isTrue();
149+
}
150+
executor.shutdown();
151+
}
152+
153+
@Test
154+
public void nonInvocableScriptEngine() throws Exception {
155+
this.view.setEngine(mock(ScriptEngine.class));
156+
this.view.setApplicationContext(this.context);
157+
}
158+
159+
@Test
160+
public void nonInvocableScriptEngineWithRenderFunction() throws Exception {
161+
this.view.setEngine(mock(ScriptEngine.class));
162+
this.view.setRenderFunction("render");
163+
assertThatIllegalArgumentException().isThrownBy(() ->
164+
this.view.setApplicationContext(this.context));
165+
}
166+
167+
@Test
168+
public void engineAndEngineNameBothDefined() {
169+
this.view.setEngine(mock(InvocableScriptEngine.class));
170+
this.view.setEngineName("test");
171+
this.view.setRenderFunction("render");
172+
assertThatIllegalArgumentException().isThrownBy(() ->
173+
this.view.setApplicationContext(this.context))
174+
.withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'.");
175+
}
176+
177+
@Test // gh-23258
178+
public void engineAndEngineSupplierBothDefined() {
179+
ScriptEngine engine = mock(InvocableScriptEngine.class);
180+
this.view.setEngineSupplier(() -> engine);
181+
this.view.setEngine(engine);
182+
this.view.setRenderFunction("render");
183+
assertThatIllegalArgumentException().isThrownBy(() ->
184+
this.view.setApplicationContext(this.context))
185+
.withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'.");
186+
}
187+
188+
@Test // gh-23258
189+
public void engineNameAndEngineSupplierBothDefined() {
190+
this.view.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
191+
this.view.setEngineName("test");
192+
this.view.setRenderFunction("render");
193+
assertThatIllegalArgumentException().isThrownBy(() ->
194+
this.view.setApplicationContext(this.context))
195+
.withMessageContaining("You should define either 'engine', 'engineSupplier', or 'engineName'.");
196+
}
197+
198+
@Test
199+
public void engineSetterAndNonSharedEngine() {
200+
this.view.setEngine(mock(InvocableScriptEngine.class));
201+
this.view.setRenderFunction("render");
202+
this.view.setSharedEngine(false);
203+
assertThatIllegalArgumentException().isThrownBy(() ->
204+
this.view.setApplicationContext(this.context))
205+
.withMessageContaining("sharedEngine");
206+
}
207+
208+
@Test
209+
public void resourceLoaderPath() {
210+
this.view.setEngine(mock(InvocableScriptEngine.class));
211+
this.view.setApplicationContext(this.context);
212+
DirectFieldAccessor viewAccessor = new DirectFieldAccessor(this.view);
213+
String[] resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths");
214+
assertThat(resourceLoaderPaths).containsExactly("classpath:");
215+
216+
this.view.setResourceLoaderPath("classpath:org/springframework/web/reactive/result/view/script/");
217+
resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths");
218+
assertThat(resourceLoaderPaths).containsExactly("classpath:org/springframework/web/reactive/result/view/script/");
219+
220+
this.view.setResourceLoaderPath("classpath:org/springframework/web/reactive/result/view/script");
221+
resourceLoaderPaths = (String[]) viewAccessor.getPropertyValue("resourceLoaderPaths");
222+
assertThat(resourceLoaderPaths).containsExactly("classpath:org/springframework/web/reactive/result/view/script/");
223+
}
224+
225+
@Test // gh-23258
226+
public void engineSupplierWithSharedEngine() {
227+
this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
228+
this.configurer.setRenderObject("Template");
229+
this.configurer.setRenderFunction("render");
230+
this.configurer.setSharedEngine(true);
231+
232+
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
233+
this.view.setApplicationContext(this.context);
234+
ScriptEngine engine1 = this.view.getEngine();
235+
ScriptEngine engine2 = this.view.getEngine();
236+
assertThat(engine1).isNotNull();
237+
assertThat(engine2).isNotNull();
238+
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
239+
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
240+
assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isTrue();
241+
}
242+
243+
@SuppressWarnings("unchecked")
244+
@Test // gh-23258
245+
public void engineSupplierWithNonSharedEngine() {
246+
this.configurer.setEngineSupplier(() -> mock(InvocableScriptEngine.class));
247+
this.configurer.setRenderObject("Template");
248+
this.configurer.setRenderFunction("render");
249+
this.configurer.setSharedEngine(false);
250+
251+
DirectFieldAccessor accessor = new DirectFieldAccessor(this.view);
252+
this.view.setApplicationContext(this.context);
253+
ScriptEngine engine1 = this.view.getEngine();
254+
ScriptEngine engine2 = this.view.getEngine();
255+
assertThat(engine1).isNotNull();
256+
assertThat(engine2).isNotNull();
257+
assertThat(accessor.getPropertyValue("renderObject")).isEqualTo("Template");
258+
assertThat(accessor.getPropertyValue("renderFunction")).isEqualTo("render");
259+
assertThat(accessor.getPropertyValue("sharedEngine")).asInstanceOf(BOOLEAN).isFalse();
260+
}
261+
262+
private interface InvocableScriptEngine extends ScriptEngine, Invocable {
263+
}
264+
265+
}

spring-webmvc/src/main/java/org/springframework/web/servlet/view/script/ScriptTemplateView.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,13 @@ public void setCharset(Charset charset) {
185185
*/
186186
public void setResourceLoaderPath(String resourceLoaderPath) {
187187
String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
188-
this.resourceLoaderPaths = new String[paths.length + 1];
189-
this.resourceLoaderPaths[0] = "";
188+
this.resourceLoaderPaths = new String[paths.length];
190189
for (int i = 0; i < paths.length; i++) {
191190
String path = paths[i];
192191
if (!path.endsWith("/") && !path.endsWith(":")) {
193192
path = path + "/";
194193
}
195-
this.resourceLoaderPaths[i + 1] = path;
194+
this.resourceLoaderPaths[i] = path;
196195
}
197196
}
198197

0 commit comments

Comments
 (0)