Skip to content

Commit

Permalink
test(config): add test docs for /config actuator
Browse files Browse the repository at this point in the history
Signed-off-by: Gaurav Mishra <[email protected]>
  • Loading branch information
GMishx committed Dec 4, 2024
1 parent 95397c9 commit e94180c
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 5 deletions.
46 changes: 46 additions & 0 deletions rest/resource-server/src/docs/asciidoc/config.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright Siemens AG, 2024. Part of the SW360 Portal Project.
//
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
//
// SPDX-License-Identifier: EPL-2.0
//
[[resources-config]]
=== Config

The config resource is used to give information from the `sw360.properties` file.
The configurations exposed are useful for the frontend UI. The backend only configs are not exposed.

[[resources-config-get]]
==== Getting config

A `GET` request will get the service's configurations.

===== Example request
include::{snippets}/should_document_get_config/curl-request.adoc[]

The response structure is the config object of the config actuator.

===== Response structure
include::{snippets}/should_document_get_config/response-fields.adoc[]

===== Example response
include::{snippets}/should_document_get_config/http-response.adoc[]

The response structure is not complete as it will grow overtime. But it is a key-value pair of strings.

[[resources-config-get-single]]
==== Getting single config value

A `GET` request will get the service's single configuration.

===== Request Parameters
include::{snippets}/should_document_get_single_config/path-parameters.adoc[]

===== Example request
include::{snippets}/should_document_get_single_config/curl-request.adoc[]

===== Example response
include::{snippets}/should_document_get_single_config/http-response.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.PathParameter;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityScheme;
Expand Down Expand Up @@ -151,7 +152,7 @@ public OpenAPI customOpenAPI() {
.responses(new ApiResponses().addApiResponse("200",
new ApiResponse().description("OK")
.content(new Content()
.addMediaType("application/json", new MediaType()
.addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, new MediaType()
.example("""
{
"status": "UP",
Expand All @@ -174,6 +175,40 @@ public OpenAPI customOpenAPI() {
.schema(new Schema<Health>())
))
))
))
.path("/config", new PathItem().get(
new Operation().tags(Collections.singletonList("Health"))
.summary("Configuration properties").operationId("get-config")
.responses(new ApiResponses().addApiResponse("200",
new ApiResponse().description("OK")
.content(new Content()
.addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, new MediaType()
.example("""
{
"property.1": "value1",
"property.2": "false",
"property.3": "value3,value4"
}
""")
.schema(new Schema<Map<String, String>>())
))
))
))
.path("/config/{key}", new PathItem().get(
new Operation().tags(Collections.singletonList("Health"))
.summary("Get single configuration property").operationId("get-single-config")
.addParametersItem(new PathParameter()
.required(true).in("path").description("Property key").example("operating.systems")
.name("key")
)
.responses(new ApiResponses().addApiResponse("200",
new ApiResponse().description("OK")
.content(new Content()
.addMediaType(org.springframework.http.MediaType.TEXT_PLAIN_VALUE, new MediaType()
.example("Android,BSD,iOS,Linux,OS X,QNX,Microsoft Windows,Windows Phone,IBM z/OS")
.schema(new Schema<Map<String, String>>())
))
))
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.util.Map;
Expand Down Expand Up @@ -78,7 +79,7 @@ public Map<String, String> config() {
return properties;
}

@ReadOperation
@ReadOperation(produces = MediaType.TEXT_PLAIN_VALUE)
public String config(@Selector String name) {
return properties.get(name);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public class ResourceServerConfiguration {
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/", "/*/*.html", "/*/*.css", "/*/*.js", "/*.js", "/*.json",
"/*/*.json", "/*/*.png", "/*/*.gif", "/*/*.ico", "/*/*.woff/*", "/*/*.ttf", "/*/*.html", "/*/*/*.html",
"/*/*.yaml", "/v3/api-docs/**", "/api/health", "/api/info");
"/*/*.yaml", "/v3/api-docs/**", "/api/health", "/api/info", "/api/config", "/api/config/*");
}

@Bean
Expand All @@ -84,6 +84,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
return http.addFilterBefore(filter, BasicAuthenticationFilter.class).authorizeHttpRequests(auth -> {
auth.requestMatchers(HttpMethod.GET, "/api/health").permitAll();
auth.requestMatchers(HttpMethod.GET, "/api/info").hasAuthority("WRITE");
auth.requestMatchers(HttpMethod.GET, "/api/config").permitAll();
auth.requestMatchers(HttpMethod.GET, "/api/config/*").permitAll();
auth.requestMatchers(HttpMethod.GET, "/api").permitAll();
auth.requestMatchers(HttpMethod.GET, "/api/reports/download").permitAll();
auth.requestMatchers(HttpMethod.GET, "/api/**").hasAuthority("READ");
Expand Down
3 changes: 2 additions & 1 deletion rest/resource-server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ management:
web:
base-path: /
exposure:
include: health,info
include: health,info,config
path-mapping:
health: /api/health
info: /api/info
config: /api/config
endpoint:
health:
show-details: always
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Siemens AG, 2024. Part of the SW360 Portal Project.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.sw360.rest.resourceserver.actuator;

import org.eclipse.sw360.rest.resourceserver.Sw360ResourceServer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Map;

import static org.assertj.core.api.BDDAssertions.then;

/* @DirtiesContext is necessary because the context needs to be reloaded inbetween the tests
otherwise the responses of previous tests are taken. NoOpCacheManager through @AutoConfigureCache
was not enough to avoid this bug.
*/
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Sw360ResourceServer.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SW360ConfigActuatorTest {

@LocalServerPort
private int port;

@SpyBean
private SW360ConfigActuator restConfigActuatorMock;

@Autowired
private TestRestTemplate testRestTemplate;

/**
* Makes a request to localhost with the default server port and returns
* the response as a response entity with type Map
* @param endpoint endpoint that will be called
* @return response of request
*/
private ResponseEntity<Map> getMapResponseEntityForHealthEndpointRequest(String endpoint) {
return this.testRestTemplate.getForEntity(
"http://localhost:" + this.port + Sw360ResourceServer.REST_BASE_PATH + endpoint, Map.class);
}

@Test
public void config_should_return_200() {
ResponseEntity<Map> entity = getMapResponseEntityForHealthEndpointRequest("/config");

then(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public SecurityFilterChain appSecurtiy(HttpSecurity httpSecurity) throws Excepti
authz -> authz
.requestMatchers(HttpMethod.GET, "/api/health").permitAll()
.requestMatchers(HttpMethod.GET, "/api/info").permitAll()
.requestMatchers(HttpMethod.GET, "/api/config").permitAll()
.requestMatchers(HttpMethod.GET, "/api/config/*").permitAll()
.anyRequest().authenticated()
).httpBasic(Customizer.withDefaults()).formLogin(Customizer.withDefaults())
.exceptionHandling(x -> x.authenticationEntryPoint(saep));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright Siemens AG, 2024. Part of the SW360 Portal Project.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.sw360.rest.resourceserver.restdocs;

import org.eclipse.sw360.rest.resourceserver.actuator.SW360ConfigActuator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.HashMap;
import java.util.Map;

import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class ConfigSpecTest extends TestRestDocsSpecBase{

@MockBean
private SW360ConfigActuator restConfigActuatorMock;

Map<String, String> properties;

{
properties = new HashMap<>();
properties.put("admin.private.project.access.enabled", "true");
properties.put("clearing.teams", "org1,org2,org3");
properties.put("rest.apitoken.read.validity.days", "90");
properties.put("rest.write.access.usergroup", "ADMIN");
}

@Test
public void should_document_get_config() throws Exception {
given(this.restConfigActuatorMock.config()).willReturn(properties);

mockMvc.perform(get("/api/config")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(this.documentationHandler.document(
responseFields(
fieldWithPath("['admin.private.project.access.enabled']").description("Sample boolean property."),
fieldWithPath("['clearing.teams']").description("Sample set property (separated by comma)."),
fieldWithPath("['rest.apitoken.read.validity.days']").description("Sample integer property."),
fieldWithPath("['rest.write.access.usergroup']").description("Sample string property.")
)
));
}

@Test
public void should_document_get_single_config() throws Exception {
given(this.restConfigActuatorMock.config("rest.apitoken.read.validity.days")).willReturn(properties.get("rest.apitoken.read.validity.days"));

mockMvc.perform(RestDocumentationRequestBuilders.get("/api/config/{key}", "rest.apitoken.read.validity.days")
.accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(header().string("Content-Type", "text/plain;charset=UTF-8"))
.andDo(this.documentationHandler.document(
pathParameters(parameterWithName("key").description("Property key"))
));
}
}
3 changes: 2 additions & 1 deletion rest/resource-server/src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ management:
web:
base-path: /
exposure:
include: health,info
include: health,info,config
path-mapping:
health: /api/health
info: /api/info
config: /api/config
endpoint:
health:
show-details: always
Expand Down

0 comments on commit e94180c

Please sign in to comment.