-
Notifications
You must be signed in to change notification settings - Fork 479
fix(velocity): fail loud when global macro library load fails (#35601) #35768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+147
−1
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
f16cac8
fix(velocity): fail loud when global macro library load fails (#35601)
dsolistorres a79866a
style(velocity): use diamond operator for ArrayList init (#35601)
dsolistorres fc5206a
fix(velocity): gate fail-loud behind Config flag, default off (#35601)
dsolistorres 0e6fcce
style(velocity): align Logger arg + pin test load-loop contract (#35601)
dsolistorres a0f36b7
style(velocity): document terminal-throw contract + pin flag-name in …
dsolistorres File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| package org.apache.velocity.runtime; | ||
|
|
||
| import com.dotmarketing.util.Config; | ||
| import org.apache.velocity.exception.ResourceNotFoundException; | ||
| import org.apache.velocity.exception.VelocityException; | ||
| import org.junit.After; | ||
| import org.junit.Before; | ||
| import org.junit.Test; | ||
| import org.mockito.MockedStatic; | ||
|
|
||
| import static org.junit.Assert.assertTrue; | ||
| import static org.junit.Assert.fail; | ||
| import static org.mockito.ArgumentMatchers.anyBoolean; | ||
| import static org.mockito.ArgumentMatchers.anyString; | ||
| import static org.mockito.ArgumentMatchers.eq; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.mockStatic; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| /** | ||
| * Tests for the {@code VELOCITY_LIBRARY_FAIL_ON_MISSING} dotCMS feature flag added in | ||
| * issue #35601. | ||
| * | ||
| * <p>The dotCMS-patched {@link VelocimacroFactory} previously swallowed | ||
| * {@link ResourceNotFoundException} when loading any configured {@code velocimacro.library} | ||
| * file, allowing engine init to succeed while leaving global macros unregistered. After | ||
| * pod restarts with transient I/O errors (notably EFS-backed K8s volumes), | ||
| * {@code #renderMarks} and {@code #editContentlet} would render as literal text on every | ||
| * page until the JVM was restarted. | ||
| * | ||
| * <p>The flag defaults to {@code false} — engine init preserves the legacy silent-warn | ||
| * behavior so existing customers are not broken by this PR. Operators opt in with | ||
| * {@code VELOCITY_LIBRARY_FAIL_ON_MISSING=true} (env var or system property) once they | ||
| * have verified their library files load cleanly. | ||
| */ | ||
| public class VelocimacroFactoryTest { | ||
|
|
||
| private static final String FLAG_KEY = "VELOCITY_LIBRARY_FAIL_ON_MISSING"; | ||
| private static final String MISSING_LIBRARY = "this-library-does-not-exist.vm"; | ||
|
|
||
| private RuntimeServices rsvc; | ||
| private MockedStatic<Config> configMock; | ||
|
|
||
| @Before | ||
| public void setUp() { | ||
| rsvc = mock(RuntimeServices.class); | ||
| when(rsvc.getProperty(RuntimeConstants.VM_LIBRARY)).thenReturn(MISSING_LIBRARY); | ||
| when(rsvc.getTemplate(MISSING_LIBRARY)) | ||
| .thenThrow(new ResourceNotFoundException( | ||
| "Cannot find resource '" + MISSING_LIBRARY + "'")); | ||
| // Permission flags read after the library loop — return the supplied default | ||
| // so init completes when the fail-on-missing flag is off. | ||
| when(rsvc.getBoolean(anyString(), anyBoolean())).thenAnswer(invocation -> | ||
| invocation.<Boolean>getArgument(1)); | ||
| configMock = mockStatic(Config.class); | ||
| // Default behavior: return the supplied default for any flag we did not stub. | ||
| configMock.when(() -> Config.getBooleanProperty(anyString(), anyBoolean())) | ||
| .thenAnswer(invocation -> invocation.<Boolean>getArgument(1)); | ||
| } | ||
|
|
||
| @After | ||
| public void tearDown() { | ||
| configMock.close(); | ||
| } | ||
|
|
||
| @Test | ||
| public void initThrowsWhenFlagIsTrue() { | ||
| configMock.when(() -> Config.getBooleanProperty(eq(FLAG_KEY), anyBoolean())) | ||
| .thenReturn(true); | ||
|
|
||
| final VelocimacroFactory factory = new VelocimacroFactory(rsvc); | ||
| try { | ||
| factory.initVelocimacro(); | ||
| fail("Expected VelocityException when " + FLAG_KEY | ||
| + "=true and a velocimacro.library file fails to load"); | ||
| } catch (VelocityException expected) { | ||
| assertTrue( | ||
| "Exception message should name the failed library: " + expected.getMessage(), | ||
| expected.getMessage().contains(MISSING_LIBRARY)); | ||
| // Pin the actionable opt-out hint so a future refactor cannot silently | ||
| // drop it from the exception message. | ||
| assertTrue( | ||
| "Exception message should reference the opt-out flag: " + expected.getMessage(), | ||
| expected.getMessage().contains(FLAG_KEY)); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void initDoesNotThrowByDefault() { | ||
| // Default behavior — flag is off, legacy silent-warn path preserved. | ||
| // The flag default (false) flows through the configMock's default thenAnswer. | ||
| final VelocimacroFactory factory = new VelocimacroFactory(rsvc); | ||
| factory.initVelocimacro(); | ||
|
|
||
| // Verify the library-loading branch actually ran — guards against future | ||
| // refactors that might short-circuit before the load loop is reached. | ||
| verify(rsvc).getTemplate(MISSING_LIBRARY); | ||
| } | ||
|
|
||
| @Test | ||
| public void initDoesNotThrowWhenFlagIsExplicitlyFalse() { | ||
| configMock.when(() -> Config.getBooleanProperty(eq(FLAG_KEY), anyBoolean())) | ||
| .thenReturn(false); | ||
|
|
||
| final VelocimacroFactory factory = new VelocimacroFactory(rsvc); | ||
| factory.initVelocimacro(); | ||
|
|
||
| verify(rsvc).getTemplate(MISSING_LIBRARY); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.