/*
* Copyright 2012-2017 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 io.spring.initializr.actuate.stat;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import io.spring.initializr.actuate.stat.MainControllerStatsIntegrationTests.StatsMockController;
import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Integration tests for stats processing.
*
* @author Stephane Nicoll
*/
@Import(StatsMockController.class)
@ActiveProfiles({ "test-default", "test-custom-stats" })
public class MainControllerStatsIntegrationTests
extends AbstractFullStackInitializrIntegrationTests {
@Autowired
private StatsMockController statsMockController;
@Autowired
private StatsProperties statsProperties;
@Before
public void setup() {
this.statsMockController.stats.clear();
// Make sure our mock is going to be invoked with the stats
this.statsProperties.getElastic().setUri("http://localhost:" + port + "/elastic");
}
@Test
public void simpleProject() {
downloadArchive("/starter.zip?groupId=com.foo&artifactId=bar&dependencies=web");
assertEquals("No stat got generated", 1, statsMockController.stats.size());
StatsMockController.Content content = statsMockController.stats.get(0);
JSONObject json = new JSONObject(content.json);
assertEquals("com.foo", json.get("groupId"));
assertEquals("bar", json.get("artifactId"));
JSONArray list = json.getJSONArray("dependencies");
assertEquals(1, list.length());
assertEquals("web", list.get(0));
}
@Test
public void authorizationHeaderIsSet() {
downloadArchive("/starter.zip");
assertEquals("No stat got generated", 1, statsMockController.stats.size());
StatsMockController.Content content = statsMockController.stats.get(0);
String authorization = content.authorization;
assertNotNull("Authorization header must be set", authorization);
assertTrue("Wrong value for authorization header",
authorization.startsWith("Basic "));
String token = authorization.substring("Basic ".length(), authorization.length());
String[] data = new String(Base64Utils.decodeFromString(token)).split(":");
assertEquals("Wrong user from " + token, "test-user", data[0]);
assertEquals("Wrong password " + token, "test-password", data[1]);
}
@Test
public void requestIpNotSetByDefault() {
downloadArchive("/starter.zip?groupId=com.foo&artifactId=bar&dependencies=web");
assertEquals("No stat got generated", 1, statsMockController.stats.size());
StatsMockController.Content content = statsMockController.stats.get(0);
JSONObject json = new JSONObject(content.json);
assertFalse("requestIp property should not be set", json.has("requestIp"));
}
@Test
public void requestIpIsSetWhenHeaderIsPresent() throws Exception {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl("/starter.zip")))
.header("X-FORWARDED-FOR", "10.0.0.123").build();
getRestTemplate().exchange(request, String.class);
assertEquals("No stat got generated", 1, statsMockController.stats.size());
StatsMockController.Content content = statsMockController.stats.get(0);
JSONObject json = new JSONObject(content.json);
assertEquals("Wrong requestIp", "10.0.0.123", json.get("requestIp"));
}
@Test
public void requestIpv4IsNotSetWhenHeaderHasGarbage() throws Exception {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl("/starter.zip")))
.header("x-forwarded-for", "foo-bar").build();
getRestTemplate().exchange(request, String.class);
assertEquals("No stat got generated", 1, statsMockController.stats.size());
StatsMockController.Content content = statsMockController.stats.get(0);
JSONObject json = new JSONObject(content.json);
assertFalse("requestIpv4 property should not be set if value is not a valid IPv4",
json.has("requestIpv4"));
}
@Test
public void requestCountryIsNotSetWhenHeaderIsSetToXX() throws Exception {
RequestEntity<?> request = RequestEntity.get(new URI(createUrl("/starter.zip")))
.header("cf-ipcountry", "XX").build();
getRestTemplate().exchange(request, String.class);
assertEquals("No stat got generated", 1, statsMockController.stats.size());
StatsMockController.Content content = statsMockController.stats.get(0);
JSONObject json = new JSONObject(content.json);
assertFalse("requestCountry property should not be set if value is set to xx",
json.has("requestCountry"));
}
@Test
public void invalidProjectSillHasStats() {
try {
downloadArchive("/starter.zip?type=invalid-type");
fail("Should have failed to generate project with invalid type");
}
catch (HttpClientErrorException ex) {
assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode());
}
assertEquals("No stat got generated", 1, statsMockController.stats.size());
StatsMockController.Content content = statsMockController.stats.get(0);
JSONObject json = new JSONObject(content.json);
assertEquals("com.example", json.get("groupId"));
assertEquals("demo", json.get("artifactId"));
assertEquals(true, json.get("invalid"));
assertEquals(true, json.get("invalidType"));
assertNotNull(json.get("errorMessage"));
assertTrue(((String) json.get("errorMessage")).contains("invalid-type"));
}
@Test
public void errorPublishingStatsDoesNotBubbleUp() {
this.statsProperties.getElastic()
.setUri("http://localhost:" + port + "/elastic-error");
downloadArchive("/starter.zip");
assertEquals("No stat should be available", 0, statsMockController.stats.size());
}
@RestController
protected static class StatsMockController {
private final List<Content> stats = new ArrayList<>();
@RequestMapping(path = "/elastic/test/my-entity", method = RequestMethod.POST)
public void handleProjectRequestDocument(RequestEntity<String> input) {
String authorization = input.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
Content content = new Content(authorization, input.getBody());
this.stats.add(content);
}
@RequestMapping(path = "/elastic-error/test/my-entity", method = RequestMethod.POST)
public void handleExpectedError() {
throw new IllegalStateException("Expected exception");
}
public static class Content {
private final String authorization;
private final String json;
Content(String authorization, String body) {
this.authorization = authorization;
json = body;
}
}
}
}