package com.music.metadata.controller; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class WebAppControllerTest { private final MockMvc mockMvc = MockMvcBuilders .standaloneSetup(new WebAppController(new DefaultResourceLoader(), "classpath:/test-empty/app/")) .build(); private final MockMvc packagedFrontendMockMvc = MockMvcBuilders .standaloneSetup(new WebAppController(new DefaultResourceLoader(), "classpath:/test-static/app/")) .build(); @Test void shouldForwardAppEntryToIndex() throws Exception { mockMvc.perform(get("/app/")) .andExpect(status().isOk()) .andExpect(forwardedUrl("/app/index.html")); } @Test void shouldForwardNestedSpaRoutesToIndex() throws Exception { mockMvc.perform(get("/app/tasks")) .andExpect(status().isOk()) .andExpect(forwardedUrl("/app/index.html")); } @Test void shouldForwardDeepNestedSpaRoutesToIndex() throws Exception { mockMvc.perform(get("/app/system/config/editor/advanced/theme")) .andExpect(status().isOk()) .andExpect(forwardedUrl("/app/index.html")); } @Test void shouldReturnNotFoundForAppIndexWhenFrontendIsNotPackaged() throws Exception { mockMvc.perform(get("/app/index.html")) .andExpect(status().isNotFound()); } @Test void shouldServePackagedAppIndexWhenFrontendExistsOnClasspath() throws Exception { packagedFrontendMockMvc.perform(get("/app/index.html")) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) .andExpect(header().string("Content-Type", org.hamcrest.Matchers.containsString("text/html"))); } @Test void shouldServePackagedAssetWhenFrontendExistsOnClasspath() throws Exception { packagedFrontendMockMvc.perform(get("/app/assets/main.js")) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.valueOf("application/javascript"))); } @Test void shouldServePackagedTopLevelStaticFileWhenFrontendExistsOnClasspath() throws Exception { packagedFrontendMockMvc.perform(get("/app/favicon.ico")) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.valueOf("image/x-icon"))); } @Test void shouldNotForwardMissingStaticAssetRequests() throws Exception { mockMvc.perform(get("/app/assets/missing.js")) .andExpect(status().isNotFound()); } @Test void shouldAllowDottedClientSegmentsToUseSpaFallback() throws Exception { mockMvc.perform(get("/app/releases/v1.0.0")) .andExpect(status().isOk()) .andExpect(forwardedUrl("/app/index.html")); } @Test void shouldTreatTerminalFileNamesAsStaticAssetRequests() throws Exception { mockMvc.perform(get("/app/assets/main.js")) .andExpect(status().isNotFound()); } @Test void shouldRejectTraversalLikeStaticAssetPaths() throws Exception { mockMvc.perform(get("/app/assets/../index.html")) .andExpect(status().isNotFound()); } @Test void shouldForwardClientRoutesBeyondFiveSegmentsToIndex() throws Exception { mockMvc.perform(get("/app/a/b/c/d/e/f/g")) .andExpect(status().isOk()) .andExpect(forwardedUrl("/app/index.html")); } }