Skip to content

Commit

Permalink
Add WebMvc tests for agent
Browse files Browse the repository at this point in the history
  • Loading branch information
PENEKhun committed Sep 2, 2024
1 parent e9f2af3 commit ee686fd
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 21 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Add security configuration to your Spring application effortlessly by simply add
3. Run your application and access the Springdog agent in your browser.
```http request
GET http://localhost:8080/springdog
GET http://localhost:8080/springdog/
// Host and port may vary depending on your application configuration.
```

Expand Down Expand Up @@ -111,12 +111,12 @@ springdog:
> The Springdog agent configuration.
| Name | Required | Description | Default | Value Sets |
|----------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------------|
| basePath | x | The base path for the agent. Used to access the springdog agent from a deployed server, such as `{{host}}/springdog`. The basePath used with this option should never be used as the controller mapping address for your application. | springdog |
| username | x | The username for the Springdog agent. Empty fields are not allowed. | admin |
| password | x | The password for the Springdog agent. Empty fields are not allowed. | admin |
| externalAccess | x | Whether to allow external access to the Springdog agent. If `false`, access from external IPs is not allowed. | false | `true` or `false` |
| Name | Required | Description | Default | Value Sets |
|----------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------------|
| basePath | x | The base path for the agent. Used to access the springdog agent from a deployed server, such as `{{host}}/springdog/`. The basePath used with this option should never be used as the controller mapping address for your application. | springdog |
| username | x | The username for the Springdog agent. Empty fields are not allowed. | admin |
| password | x | The password for the Springdog agent. Empty fields are not allowed. | admin |
| externalAccess | x | Whether to allow external access to the Springdog agent. If `false`, access from external IPs is not allowed. | false | `true` or `false` |

### springdog.notification.gmail

Expand Down
2 changes: 1 addition & 1 deletion springdog-project/springdog-agent/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ shadowJar {
}

dependencies {
// implementation "org.springframework.boot:spring-boot-starter-web:${springbootVersion}"
compileOnly "org.springframework.boot:spring-boot-starter-web:${springbootVersion}"
implementation "org.springframework.boot:spring-boot-starter-security:${springbootVersion}"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity${thymeleafSecurityVersion}"
Expand All @@ -17,6 +16,7 @@ dependencies {
implementation("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:${thymeleafLayoutDialectVersion}") {
exclude group: 'nz.net.ultraq.thymeleaf', module: 'thymeleaf-layout-dialect-extensions'
}
testImplementation "org.springframework.boot:spring-boot-starter-web:${springbootVersion}"
testImplementation "org.springframework.boot:spring-boot-starter-test:${springbootVersion}"
testImplementation "org.springframework.security:spring-security-test:${springVersion}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@

package org.easypeelsecurity.springdog.agent;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation for Springdog agent controller.
* related to {@link SpringdogDynamicUrlMappingConfig}
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SpringdogAgentController {
@interface SpringdogAgentController {
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
/**
* Springdog dynamic URL mapping configuration.
* Enable agent access to the springdog path user determine at runtime.
* related to {@link SpringdogAgentController}
*/
@Component
public class SpringdogDynamicUrlMappingConfig implements WebMvcRegistrations {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
@Configuration
public class SpringdogSecurityConfig {

public static final String SPRINGDOG_AGENT_ADMIN_ROLE = "SPRINGDOG_AGENT_ADMIN";
private final SpringdogProperties springdogProperties;

/**
Expand All @@ -56,7 +57,7 @@ public SecurityFilterChain springdogSecurityFilterChain(HttpSecurity http) throw
String baseAbsolutePath = springdogProperties.computeAbsolutePath("");
http
.securityMatcher(baseAbsolutePath + "**")
.authorizeHttpRequests(auth -> auth.anyRequest().hasRole("SPRINGDOG_AGENT_ADMIN"))
.authorizeHttpRequests(auth -> auth.anyRequest().hasRole(SPRINGDOG_AGENT_ADMIN_ROLE))
.formLogin(formLogin -> formLogin
.loginPage(springdogProperties.computeAbsolutePath("/login"))
.failureUrl(springdogProperties.computeAbsolutePath("/login?error"))
Expand Down Expand Up @@ -90,7 +91,7 @@ public UserDetailsService springdogUserDetailsService() {
String password = springdogProperties.getAgentPassword();
UserDetails user = User.withUsername(username)
.password("{noop}" + password)
.roles("SPRINGDOG_AGENT_ADMIN")
.roles(SPRINGDOG_AGENT_ADMIN_ROLE)
.build();

return new InMemoryUserDetailsManager(user);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fixture;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import org.easypeelsecurity.springdog.shared.dto.SystemMetricDto;
import org.easypeelsecurity.springdog.shared.vo.DashboardResponse;
import org.easypeelsecurity.springdog.shared.vo.DashboardResponse.DailyEndpointMetric;
import org.easypeelsecurity.springdog.shared.vo.DashboardResponse.DailySlowestEndpoint;
import org.easypeelsecurity.springdog.shared.vo.DashboardResponse.DailyTopFailWithRatelimitEndpoint;
import org.easypeelsecurity.springdog.shared.vo.DashboardResponse.DailyTopTrafficEndpoint;

@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
public class DashBoardResponseFixture {
public static DashboardResponse get() {
SystemMetricDto systemMetric = new SystemMetricDto(10.0, 20.0, 30.0, LocalDateTime.now());
var dailyEndpointMetric = new DailyEndpointMetric(1, 2, 3, LocalDate.now());
var dailyTopTrafficEndpoint = new DailyTopTrafficEndpoint("/test", "GET", 1);
var dailySlowestEndpoint = new DailySlowestEndpoint("/test", "GET", 1);
var dailyTopFailWithRatelimitEndpoint = new DailyTopFailWithRatelimitEndpoint("/test", "GET", 1);

return new DashboardResponse(1, 2, 3,
List.of(systemMetric),
List.of(dailyEndpointMetric),
List.of(dailyTopTrafficEndpoint), List.of(dailySlowestEndpoint),
List.of(dailyTopFailWithRatelimitEndpoint));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.easypeelsecurity.springdog.agent;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import org.easypeelsecurity.springdog.agent.security.SpringdogSecurityConfig;
import org.easypeelsecurity.springdog.domain.ratelimit.EndpointService;
import org.easypeelsecurity.springdog.domain.statistics.StatisticsService;
import org.easypeelsecurity.springdog.shared.dto.EndpointDto;
import org.easypeelsecurity.springdog.shared.enums.HttpMethod;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import fixture.DashBoardResponseFixture;

@SpringBootTest(properties = "springdog.agent.base-path=springdog")
@Import({TestConfig.class, SpringdogSecurityConfig.class})
class SpringdogAgentViewTest {
MockMvc mockMvc;

@Autowired
WebApplicationContext context;

@MockBean
StatisticsService statisticsService;

@MockBean
EndpointService endpointService;

@BeforeEach
void setUp() {
this.mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}

@ParameterizedTest
@CsvSource({
"/",
"/aidwjaiwjdawid",
"/login",
})
void invalidPage(String invalidPath) throws Exception {
mockMvc.perform(get("/login"))
.andExpect(status().isNotFound());
}

@Test
@DisplayName("Inaccessible without permission")
void withoutAuthentication() throws Exception {
mockMvc.perform(get("/springdog/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/springdog/login"));
}

@Test
@WithMockUser(username = "admin", roles = {SpringdogSecurityConfig.SPRINGDOG_AGENT_ADMIN_ROLE})
void testHomePage() throws Exception {
when(endpointService.getAllChangeLogsNotResolved()).thenReturn(Collections.emptyList());
when(statisticsService.getDashboardResponse(any())).thenReturn(DashBoardResponseFixture.get());

mockMvc.perform(get("/springdog/"))
.andExpect(status().isOk())
.andExpect(view().name("/templates/content/main.html"))
.andExpect(model().attributeExists("endpointChangeLog", "totalEndpointCount"));
}

@Test
@WithMockUser(username = "admin", roles = {SpringdogSecurityConfig.SPRINGDOG_AGENT_ADMIN_ROLE})
void ratelimitList() throws Exception {
when(endpointService.findAllEndpoints()).thenReturn(List.of());

mockMvc.perform(get("/springdog/rate-limit"))
.andExpect(status().isOk())
.andExpect(view().name("/templates/content/rate-limit/manage.html"))
.andExpect(model().attributeExists("endpoints"));
}

@Test
@WithMockUser(username = "admin", roles = {SpringdogSecurityConfig.SPRINGDOG_AGENT_ADMIN_ROLE})
void setRateLimitSpecific() throws Exception {
long endpointId = 1L;
when(endpointService.findEndpoint(endpointId)).thenReturn(
new EndpointDto.Builder()
.id(endpointId)
.path("/test")
.methodSignature("org.a.b.C.d()")
.httpMethod(HttpMethod.GET)
.build()
);

mockMvc.perform(get("/springdog/rate-limit/{endpointId}", endpointId))
.andExpect(status().isOk())
.andExpect(view().name("/templates/content/rate-limit/actions/setting.html"))
.andExpect(model().attributeExists("endpoint"));
}

@Test
@WithMockUser(username = "admin", roles = {SpringdogSecurityConfig.SPRINGDOG_AGENT_ADMIN_ROLE})
void viewRatelimitEndpointAnalytics() throws Exception {
long endpointId = 1L;
when(endpointService.findEndpoint(endpointId)).thenReturn(
new EndpointDto.Builder()
.id(endpointId)
.path("/test")
.methodSignature("org.a.b.C.d()")
.httpMethod(HttpMethod.GET)
.build()
);

mockMvc.perform(get("/springdog/rate-limit/{endpointId}/analytics", endpointId))
.andExpect(status().isOk())
.andExpect(view().name("/templates/content/rate-limit/actions/analytics.html"))
.andExpect(model().attributeExists("endpoint"));
}

@Test
void testLogin() throws Exception {
mockMvc.perform(get("/springdog/rate-limit"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/springdog/login"));
}

@Test
@WithMockUser(username = "admin", roles = {SpringdogSecurityConfig.SPRINGDOG_AGENT_ADMIN_ROLE})
void testLogout() throws Exception {
mockMvc.perform(get("/springdog/logout"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/springdog/login?logout"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.easypeelsecurity.springdog.agent;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.ViewResolver;

import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

/**
* Test configuration for agent test.
*/
@SpringBootApplication
@ComponentScan(basePackages = {"org.easypeelsecurity.springdog.shared", "org.easypeelsecurity.springdog.agent"})
public class TestConfig {

@Bean
public ViewResolver viewResolver() {
ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix("");
templateResolver.setSuffix("");
templateResolver.setTemplateMode(TemplateMode.HTML);

ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine(templateResolver));
return viewResolver;
}

@Bean
public SpringTemplateEngine templateEngine(ClassLoaderTemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
}
Loading

0 comments on commit ee686fd

Please sign in to comment.