Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions metarParser-services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
<artifactId>metarParser-services</artifactId>

<properties>
<jacoco.coverage.instruction.minimum>0.94</jacoco.coverage.instruction.minimum>
<jacoco.coverage.instruction.minimum>0.93</jacoco.coverage.instruction.minimum>
<jacoco.coverage.branch.minimum>1</jacoco.coverage.branch.minimum>
<jacoco.coverage.complexity.minimum>0.90</jacoco.coverage.complexity.minimum>
<jacoco.coverage.complexity.minimum>0.88</jacoco.coverage.complexity.minimum>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,49 @@ public abstract class AbstractWeatherProvider implements WeatherProvider {
/** The required length of a valid ICAO code. */
static final int ICAO_LENGTH = 4;

/** The default User-Agent string sent with every HTTP request. */
static final String DEFAULT_USER_AGENT = "MetarParser";

/** The User-Agent string sent with every HTTP request. */
private final String userAgent;

/** The HTTP client used to execute requests. */
private final HttpClient httpClient;

/**
* Protected constructor.
* Protected constructor. Uses the default User-Agent and a default {@link HttpClient}.
*/
protected AbstractWeatherProvider() {
this(DEFAULT_USER_AGENT, HttpClient.newBuilder().build());
}

/**
* Protected constructor with a custom User-Agent string and a default {@link HttpClient}.
*
* @param userAgent the User-Agent header value to send with every HTTP request.
*/
protected AbstractWeatherProvider(final String userAgent) {
this(userAgent, HttpClient.newBuilder().build());
}

/**
* Package-private constructor for testing. Accepts an injectable {@link HttpClient}.
*
* @param httpClient the HTTP client to use for all requests.
*/
AbstractWeatherProvider(final HttpClient httpClient) {
this(DEFAULT_USER_AGENT, httpClient);
}

/**
* Private canonical constructor used by all other constructors.
*
* @param userAgent the User-Agent header value to send with every HTTP request.
* @param httpClient the HTTP client to use for all requests.
*/
private AbstractWeatherProvider(final String userAgent, final HttpClient httpClient) {
this.userAgent = userAgent;
this.httpClient = httpClient;
}

/**
Expand All @@ -42,7 +81,7 @@ protected final void checkIcao(final String icao) throws ParseException {
}

/**
* Builds an HTTP GET request for the given URL.
* Builds an HTTP GET request for the given URL, including the configured {@code User-Agent} header.
*
* @param url the URL to request.
* @return the constructed {@link HttpRequest}.
Expand All @@ -53,6 +92,7 @@ protected final HttpRequest buildRequest(final String url) throws URISyntaxExcep
.uri(new URI(url))
.GET()
.version(HttpClient.Version.HTTP_2)
.header("User-Agent", userAgent)
.build();
}

Expand All @@ -70,9 +110,7 @@ protected final HttpRequest buildRequest(final String url) throws URISyntaxExcep
protected final HttpResponse<Stream<String>> getHttpResponse(final String url)
throws IOException, URISyntaxException, InterruptedException, ParseException {
HttpRequest request = buildRequest(url);
HttpResponse<Stream<String>> response = HttpClient.newBuilder()
.build()
.send(request, HttpResponse.BodyHandlers.ofLines());
HttpResponse<Stream<String>> response = httpClient.send(request, HttpResponse.BodyHandlers.ofLines());
if (response.statusCode() != HttpURLConnection.HTTP_OK) {
throw new ParseException(ErrorCodes.ERROR_CODE_INVALID_ICAO);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@ public final class AviationWeatherProvider extends AbstractWeatherProvider {
/** The "SPECI " report-type prefix returned by the API. */
private static final String SPECI_PREFIX = "SPECI ";

/**
* Default constructor. Uses the default User-Agent.
*/
public AviationWeatherProvider() {
super();
}

/**
* Constructor with a custom User-Agent string.
*
* @param userAgent the User-Agent header value to send with every HTTP request.
*/
public AviationWeatherProvider(final String userAgent) {
super(userAgent);
}

/**
* Package-private constructor for testing. Accepts an injectable {@link java.net.http.HttpClient}.
*
* @param httpClient the HTTP client to use for all requests.
*/
AviationWeatherProvider(final java.net.http.HttpClient httpClient) {
super(httpClient);
}

@Override
public String retrieveMetar(final String icao) throws ParseException, IOException, URISyntaxException, InterruptedException {
checkIcao(icao);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@ public final class NOAAWeatherProvider extends AbstractWeatherProvider {
/** The AMD TAF token that appears as a second line in amended NOAA TAF responses. */
private static final String AMD_TAF_TOKEN = "AMD TAF";

/**
* Default constructor. Uses the default User-Agent.
*/
public NOAAWeatherProvider() {
super();
}

/**
* Constructor with a custom User-Agent string.
*
* @param userAgent the User-Agent header value to send with every HTTP request.
*/
public NOAAWeatherProvider(final String userAgent) {
super(userAgent);
}

/**
* Package-private constructor for testing. Accepts an injectable {@link java.net.http.HttpClient}.
*
* @param httpClient the HTTP client to use for all requests.
*/
NOAAWeatherProvider(final java.net.http.HttpClient httpClient) {
super(httpClient);
}

@Override
public String retrieveMetar(final String icao) throws ParseException, IOException, URISyntaxException, InterruptedException {
checkIcao(icao);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,31 @@
import io.github.mivek.model.AbstractWeatherCode;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URISyntaxException;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

abstract class AbstractWeatherCodeServiceTest<T extends AbstractWeatherCode> {
protected abstract AbstractWeatherCodeService<T> getService();
protected abstract AbstractWeatherCodeService<T> getService(FakeWeatherProvider provider);

@Test
void testRetrieveFromAirportInvalid() {
ParseException e = assertThrows(ParseException.class, () -> getService().retrieveFromAirport("RandomIcao"));
ParseException e = assertThrows(ParseException.class, () -> getService(new FakeWeatherProvider()).retrieveFromAirport("RandomIcao"));
assertEquals(ErrorCodes.ERROR_CODE_INVALID_ICAO, e.getErrorCode());
}

@Test
void testRetrieveFromAirport() throws IOException, ParseException, URISyntaxException, InterruptedException {
T res = getService().retrieveFromAirport("LFPG");
void testRetrieveFromAirport() throws Exception {
T res = getService(new FakeWeatherProvider()).retrieveFromAirport("LFPG");
assertThat(res, notNullValue());
assertThat(res.getAirport().getIcao(), is("LFPG"));
}

@Test
void testRetrieveFromAirportNotFound() {
ParseException e = assertThrows(ParseException.class, () -> getService().retrieveFromAirport("lftm"));
ParseException e = assertThrows(ParseException.class, () -> getService(new FakeWeatherProvider()).retrieveFromAirport("lftm"));
assertEquals(ErrorCodes.ERROR_CODE_INVALID_ICAO, e.getErrorCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.github.mivek.service;

import io.github.mivek.exception.ErrorCodes;
import io.github.mivek.exception.ParseException;
import io.github.mivek.service.provider.WeatherProvider;

/**
* Test-only {@link WeatherProvider} implementation that returns hardcoded weather strings.
* Used to avoid real HTTP calls in service-layer tests.
*/
final class FakeWeatherProvider implements WeatherProvider {

/** Hardcoded METAR string for LFPG. */
static final String LFPG_METAR = "LFPG 251830Z 17013KT 9999 OVC006 04/03 Q1012 NOSIG";

/** Hardcoded TAF string for LFPG. */
static final String LFPG_TAF = "TAF LFPG 121700Z 1218/1324 13003KT CAVOK TX09/1315Z TN00/1306Z\nTEMPO 1303/1308 4000 BR";

@Override
public String retrieveMetar(final String icao) throws ParseException {
if (icao.length() != 4) {
throw new ParseException(ErrorCodes.ERROR_CODE_INVALID_ICAO);
}
if (!"LFPG".equalsIgnoreCase(icao)) {
throw new ParseException(ErrorCodes.ERROR_CODE_INVALID_ICAO);
}
return LFPG_METAR;
}

@Override
public String retrieveTaf(final String icao) throws ParseException {
if (icao.length() != 4) {
throw new ParseException(ErrorCodes.ERROR_CODE_INVALID_ICAO);
}
if (!"LFPG".equalsIgnoreCase(icao)) {
throw new ParseException(ErrorCodes.ERROR_CODE_INVALID_ICAO);
}
return LFPG_TAF;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@
import io.github.mivek.exception.ParseException;
import io.github.mivek.internationalization.Messages;
import io.github.mivek.model.Metar;
import io.github.mivek.service.provider.AviationWeatherProvider;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URISyntaxException;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.*;
Expand Down Expand Up @@ -45,15 +41,8 @@ void testDecodeValidMetar() throws ParseException {

}

@Test
void testRetrieveFromAirportWithAviationWeatherProvider() throws ParseException, IOException, URISyntaxException, InterruptedException {
Metar result = MetarService.withProvider(new AviationWeatherProvider()).retrieveFromAirport("LFPG");
assertNotNull(result);
assertEquals("LFPG", result.getAirport().getIcao());
}

@Override
protected AbstractWeatherCodeService<Metar> getService() {
return MetarService.getInstance();
protected AbstractWeatherCodeService<Metar> getService(final FakeWeatherProvider provider) {
return MetarService.withProvider(provider);
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
package io.github.mivek.service;

import io.github.mivek.exception.ParseException;
import io.github.mivek.model.TAF;
import io.github.mivek.service.provider.AviationWeatherProvider;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URISyntaxException;

import static org.junit.jupiter.api.Assertions.assertNotNull;

class TAFServiceTest extends AbstractWeatherCodeServiceTest<TAF> {

private final TAFService sut = TAFService.getInstance();

@Override
protected AbstractWeatherCodeService<TAF> getService() {
return sut;
protected AbstractWeatherCodeService<TAF> getService(final FakeWeatherProvider provider) {
return TAFService.withProvider(provider);
}

@Test
void testRetrieveFromAirportWithAviationWeatherProvider() throws ParseException, IOException, URISyntaxException, InterruptedException {
TAF result = TAFService.withProvider(new AviationWeatherProvider()).retrieveFromAirport("LFPG");
void testRetrieveFromAirportWithFakeProvider() throws Exception {
TAF result = TAFService.withProvider(new FakeWeatherProvider()).retrieveFromAirport("LFPG");
assertNotNull(result);
assertNotNull(result.getAirport());
}
Expand Down
Loading
Loading