2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.fmiweather;
15 import static org.junit.jupiter.api.Assertions.fail;
17 import java.io.BufferedReader;
18 import java.io.IOException;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.net.URISyntaxException;
22 import java.nio.charset.StandardCharsets;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.Paths;
26 import java.util.HashSet;
27 import java.util.Objects;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.hamcrest.Description;
33 import org.hamcrest.Matcher;
34 import org.hamcrest.TypeSafeMatcher;
35 import org.junit.jupiter.api.BeforeEach;
36 import org.openhab.binding.fmiweather.internal.client.Client;
37 import org.openhab.binding.fmiweather.internal.client.Data;
38 import org.openhab.binding.fmiweather.internal.client.FMIResponse;
39 import org.openhab.binding.fmiweather.internal.client.Location;
42 * Base class for response parsing tests
44 * @author Sami Salonen - Initial contribution
47 public class AbstractFMIResponseParsingTest {
50 protected Client client;
53 public void setUpClient() {
54 client = new Client();
57 protected Path getTestResource(String filename) {
59 return Paths.get(getClass().getResource(filename).toURI());
60 } catch (URISyntaxException e) {
62 // Make the compiler happy by throwing here, fails already above
63 throw new IllegalStateException();
67 protected String readTestResourceUtf8(String filename) {
68 return readTestResourceUtf8(getTestResource(filename));
71 protected String readTestResourceUtf8(Path path) {
73 BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);
74 StringBuilder content = new StringBuilder();
75 char[] buffer = new char[1024];
77 while ((read = reader.read(buffer)) != -1) {
78 content.append(buffer, 0, read);
80 return content.toString();
81 } catch (IOException e) {
83 // Make the compiler happy by throwing here, fails already above
84 throw new IllegalStateException();
88 protected static TypeSafeMatcher<Location> deeplyEqualTo(Location location) {
89 return new ResponseLocationMatcher(location);
92 protected static Matcher<Data> deeplyEqualTo(long start, int intervalMinutes, String... values) {
93 return new TypeSafeMatcher<Data>() {
95 private TimestampMatcher timestampMatcher = new TimestampMatcher(start, intervalMinutes, values.length);
96 private ValuesMatcher valuesMatcher = new ValuesMatcher(values);
99 public void describeTo(@Nullable Description description) {
100 if (description == null) {
103 description.appendDescriptionOf(timestampMatcher);
104 description.appendText(" and ");
105 description.appendDescriptionOf(valuesMatcher);
109 protected boolean matchesSafely(Data dataValues) {
110 return timestampMatcher.matches(dataValues.timestampsEpochSecs)
111 && valuesMatcher.matches(dataValues.values);
115 protected void describeMismatchSafely(Data dataValues, @Nullable Description mismatchDescription) {
116 if (mismatchDescription == null) {
117 super.describeMismatchSafely(dataValues, mismatchDescription);
120 if (!timestampMatcher.matches(dataValues.timestampsEpochSecs)) {
121 mismatchDescription.appendText("timestamps mismatch: ");
122 if (dataValues.timestampsEpochSecs[0] != start) {
123 mismatchDescription.appendText("start mismatch (was ");
124 mismatchDescription.appendValue(dataValues.timestampsEpochSecs[0]);
125 mismatchDescription.appendText(")");
126 } else if (dataValues.timestampsEpochSecs.length != values.length) {
127 mismatchDescription.appendText("length mismatch (was ");
128 mismatchDescription.appendValue(dataValues.timestampsEpochSecs.length);
129 mismatchDescription.appendText(")");
131 mismatchDescription.appendText("interval mismatch (was ");
132 Set<Long> intervals = new HashSet<>();
133 for (int i = 1; i < values.length; i++) {
134 long interval = dataValues.timestampsEpochSecs[i] - dataValues.timestampsEpochSecs[i - 1];
135 intervals.add(interval);
137 mismatchDescription.appendValue(intervals.toArray());
138 mismatchDescription.appendText(")");
141 mismatchDescription.appendText(", valuesMatch=").appendValue(valuesMatcher.matches(dataValues.values));
150 * @throws Throwable exception raised by parseMultiPointCoverageXml
151 * @throws AssertionError exception raised when parseMultiPointCoverageXml method signature does not match excepted
152 * (test & implementation is out-of-sync)
154 protected FMIResponse parseMultiPointCoverageXml(String content) throws Throwable {
156 Method parseMethod = Client.class.getDeclaredMethod("parseMultiPointCoverageXml", String.class);
157 parseMethod.setAccessible(true);
158 return Objects.requireNonNull((FMIResponse) parseMethod.invoke(client, content));
159 } catch (InvocationTargetException e) {
160 throw e.getTargetException();
161 } catch (Exception e) {
162 fail(String.format("Unexpected reflection error (code changed?) %s: %s", e.getClass().getName(),
164 // Make the compiler happy by throwing here, fails already above
165 throw new IllegalStateException();
169 @SuppressWarnings("unchecked")
170 protected Set<Location> parseStations(String content) {
172 Method parseMethod = Objects.requireNonNull(Client.class.getDeclaredMethod("parseStations", String.class));
173 parseMethod.setAccessible(true);
174 return Objects.requireNonNull((Set<Location>) parseMethod.invoke(client, content));
175 } catch (InvocationTargetException e) {
176 throw new RuntimeException(e.getTargetException());
177 } catch (Exception e) {
178 fail(String.format("Unexpected reflection error (code changed?) %s: %s", e.getClass().getName(),
180 // Make the compiler happy by throwing here, fails already above
181 throw new IllegalStateException();