Skip to content

Commit d727516

Browse files
committed
fail on an exception and test it better
1 parent 3d4a37e commit d727516

File tree

4 files changed

+74
-41
lines changed

4 files changed

+74
-41
lines changed

.idea/modules.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.gradle

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,13 @@ dependencies {
3737
testCompileOnly 'org.projectlombok:lombok:1.18.36'
3838
testAnnotationProcessor 'org.projectlombok:lombok:1.18.36'
3939

40-
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
41-
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
40+
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
41+
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
42+
testImplementation 'org.assertj:assertj-core:3.17.2'
43+
testImplementation 'org.mockito:mockito-core:3.5.13'
44+
45+
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
46+
4247
testImplementation 'org.slf4j:slf4j-api:1.7.36'
4348
testImplementation 'org.slf4j:slf4j-simple:1.7.36'
4449

@@ -88,21 +93,3 @@ publishing {
8893
}
8994
}
9095
}
91-
92-
jreleaser {
93-
signing {
94-
active = 'ALWAYS'
95-
armored = true
96-
}
97-
deploy {
98-
maven {
99-
mavenCentral {
100-
sonatype {
101-
active = 'ALWAYS'
102-
url = 'https://central.sonatype.com/api/v1/publisher'
103-
stagingRepository('build/staging-deploy')
104-
}
105-
}
106-
}
107-
}
108-
}

src/main/java/ch/akop/weathercloud/scraper/weathercloud/Scraper.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.reactivex.rxjava3.core.Observable;
1919
import io.reactivex.rxjava3.schedulers.Schedulers;
2020
import lombok.SneakyThrows;
21+
import lombok.extern.slf4j.Slf4j;
2122

2223
import java.io.IOException;
2324
import java.net.URI;
@@ -30,11 +31,11 @@
3031
import java.time.ZoneId;
3132
import java.time.ZonedDateTime;
3233
import java.util.concurrent.TimeUnit;
33-
import java.util.concurrent.atomic.AtomicBoolean;
3434

3535
/**
3636
* Use this scraper to fetch data from Weathercloud.
3737
*/
38+
@Slf4j
3839
public class Scraper {
3940

4041
private static final String BASE_URL = "https://app.weathercloud.net/device/stats?code=";
@@ -102,24 +103,19 @@ private String doRequestAndGetBody(String deviceId) throws URISyntaxException, I
102103

103104
/**
104105
* Pull weather-data at a constant rate. It will fetch the first dataset immediately.
106+
* On an exception, the Observable will be stopped. The caller needs a logic to recover from a bad state.
105107
*
106108
* @param deviceId numeric deviceId, like 1234567890
107109
* @param rate Duration, how often data should be fetched.
108110
* @return A flowable, which will emit on each new value (same values will not be emitted twice)
109111
*/
110-
public Flowable<Weather> scrape$(String deviceId, Duration rate) {
111-
// because of backoff-logic, we need to prevent that the timer-emits will stack up
112-
AtomicBoolean isWorking = new AtomicBoolean(false);
112+
public static Flowable<Weather> scrape$(String deviceId, Duration rate, Scraper scraper) {
113113
return Observable.interval(0, rate.toMillis(), TimeUnit.MILLISECONDS, Schedulers.io())
114-
.filter(aLong -> !isWorking.get())
115-
.flatMap(ignored -> {
116-
isWorking.set(true);
117-
118-
return Observable.fromSupplier(() -> this.scrape(deviceId))
119-
.doOnComplete(() -> isWorking.set(false))
120-
.subscribeOn(Schedulers.computation());
121-
})
114+
.concatMap(ignored -> Observable.fromSupplier(() -> scraper.scrape(deviceId))
115+
.subscribeOn(Schedulers.computation())
116+
)
117+
.onErrorComplete()
122118
.distinct(Weather::getRecordedAt)
123-
.toFlowable(BackpressureStrategy.DROP);
119+
.toFlowable(BackpressureStrategy.BUFFER);
124120
}
125121
}
Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,82 @@
11
package ch.akop.weathercloud.scraper.weathercloud;
22

3+
import ch.akop.weathercloud.Weather;
4+
import ch.akop.weathercloud.light.Light;
5+
import ch.akop.weathercloud.light.LightUnit;
6+
import ch.akop.weathercloud.rain.Rain;
7+
import ch.akop.weathercloud.rain.RainUnit;
8+
import ch.akop.weathercloud.temperature.Temperature;
9+
import ch.akop.weathercloud.temperature.TemperatureUnit;
10+
import ch.akop.weathercloud.wind.Wind;
11+
import ch.akop.weathercloud.wind.WindSpeedUnit;
12+
import io.reactivex.rxjava3.observers.TestObserver;
313
import lombok.extern.slf4j.Slf4j;
4-
import org.junit.jupiter.api.Disabled;
14+
import org.junit.jupiter.api.BeforeEach;
515
import org.junit.jupiter.api.Test;
616

17+
import java.math.BigDecimal;
718
import java.time.Duration;
19+
import java.time.ZonedDateTime;
820
import java.time.temporal.ChronoUnit;
921

22+
import static org.assertj.core.api.Assertions.assertThat;
1023
import static org.junit.jupiter.api.Assertions.assertNotNull;
24+
import static org.mockito.ArgumentMatchers.anyString;
25+
import static org.mockito.Mockito.doReturn;
26+
import static org.mockito.Mockito.spy;
1127

1228
@Slf4j
1329
class ScraperTest {
1430

31+
Scraper testee;
32+
33+
@BeforeEach
34+
void setup() {
35+
testee = spy(new Scraper());
36+
}
37+
1538
@Test
1639
void manual_test() {
17-
var testee = new Scraper();
1840
var result = testee.scrape("7003523537");
1941
assertNotNull(result);
2042
}
2143

2244
@Test
23-
@Disabled("Run manually")
24-
void manual_scheduler_test() throws InterruptedException {
25-
var testee = new Scraper();
45+
public void manual_scheduler_test() throws InterruptedException {
46+
var value1 = new Weather()
47+
.setRecordedAt(ZonedDateTime.now())
48+
.setLight(Light.fromUnit(BigDecimal.ZERO, LightUnit.KILO_LUX))
49+
.setOuterTemperatur(Temperature.fromUnit(BigDecimal.ZERO, TemperatureUnit.DEGREE))
50+
.setWind(Wind.fromUnit(BigDecimal.ZERO, WindSpeedUnit.KILOMETERS_PER_SECOND))
51+
.setRain(Rain.fromUnit(BigDecimal.ZERO, RainUnit.MILLIMETER_PER_HOUR));
52+
53+
var value2 = new Weather()
54+
.setRecordedAt(ZonedDateTime.now().plusHours(12))
55+
.setLight(Light.fromUnit(BigDecimal.ONE, LightUnit.KILO_LUX))
56+
.setOuterTemperatur(Temperature.fromUnit(BigDecimal.ONE, TemperatureUnit.DEGREE))
57+
.setWind(Wind.fromUnit(BigDecimal.ONE, WindSpeedUnit.KILOMETERS_PER_SECOND))
58+
.setRain(Rain.fromUnit(BigDecimal.ONE, RainUnit.MILLIMETER_PER_HOUR));
59+
60+
61+
doReturn(value1)
62+
.doThrow(new RuntimeException("blub"))
63+
.doReturn(value1)
64+
.doReturn(value2)
65+
.when(testee)
66+
.scrape(anyString());
67+
68+
69+
var tester = new TestObserver<Weather>();
70+
Scraper.scrape$("7003523537", Duration.of(1, ChronoUnit.SECONDS), testee)
71+
.doOnError(e -> log.warn("Hi"))
72+
.toObservable()
73+
.subscribe(tester);
2674

27-
testee.scrape$("7003523537", Duration.of(15, ChronoUnit.SECONDS))
28-
.subscribe(weather -> log.info("In Oensingen, we had {}", weather.getOuterTemperatur()));
75+
Thread.sleep(100);
76+
tester.assertValuesOnly(value1);
77+
Thread.sleep(1500);
2978

30-
Thread.sleep(600000);
79+
tester.assertComplete();
3180
}
3281

3382
}

0 commit comments

Comments
 (0)