/**
* Copyright (c) 2014-2016 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.binding.ntp.test;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.binding.ntp.NtpBindingConstants;
import org.eclipse.smarthome.binding.ntp.handler.NtpHandler;
import org.eclipse.smarthome.config.core.Configuration;
import org.eclipse.smarthome.core.events.Event;
import org.eclipse.smarthome.core.events.EventSubscriber;
import org.eclipse.smarthome.core.items.GenericItem;
import org.eclipse.smarthome.core.items.Item;
import org.eclipse.smarthome.core.items.ItemNotFoundException;
import org.eclipse.smarthome.core.items.ItemRegistry;
import org.eclipse.smarthome.core.items.events.AbstractItemEventSubscriber;
import org.eclipse.smarthome.core.library.items.DateTimeItem;
import org.eclipse.smarthome.core.library.items.StringItem;
import org.eclipse.smarthome.core.library.types.DateTimeType;
import org.eclipse.smarthome.core.library.types.StringType;
import org.eclipse.smarthome.core.thing.Channel;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.ManagedThingProvider;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingProvider;
import org.eclipse.smarthome.core.thing.ThingRegistry;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.core.thing.binding.ThingHandler;
import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder;
import org.eclipse.smarthome.core.thing.link.ItemChannelLink;
import org.eclipse.smarthome.core.thing.link.ManagedItemChannelLinkProvider;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.test.java.JavaOSGiTest;
import org.eclipse.smarthome.test.storage.VolatileStorageService;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
/**
* OSGi tests for the {@link NtpHandler}
*
* @author Petar Valchev - Initial Contribution
* @author Markus Rathgeb - Migrated tests from Groovy to pure Java
*/
public class NtpOSGiTest extends JavaOSGiTest {
private static TimeZone systemTimeZone;
private static Locale locale;
private EventSubscriberMock eventSubscriberMock;
private NtpHandler ntpHandler;
private Thing ntpThing;
private GenericItem testItem;
private ManagedThingProvider managedThingProvider;
private ThingRegistry thingRegistry;
private ItemRegistry itemRegistry;
private static final String DEFAULT_TIME_ZONE_ID = "Europe/Helsinki";
private static final String TEST_TIME_ZONE_ID = "America/Los_Angeles";
private static final String TEST_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss z";
private static final String TEST_ITEM_NAME = "testItem";
private static final String TEST_THING_ID = "testThingId";
// No bundle in ESH is exporting a package from which we can use item types as constants, so we will use String.
private static final String ACCEPTED_ITEM_TYPE_STRING = "String";
private static final String ACCEPTED_ITEM_TYPE_DATE_TIME = "DateTime";
enum UpdateEventType {
HANDLE_COMMAND("handleCommand"),
CHANNEL_LINKED("channelLinked");
private String updateEventType;
private UpdateEventType(String updateEventType) {
this.updateEventType = updateEventType;
}
public String getUpdateEventType() {
return updateEventType;
}
}
@BeforeClass
public static void setUpClass() {
/*
* Store the initial system time zone and locale value,
* so that we can restore them at the test end.
*/
systemTimeZone = TimeZone.getDefault();
locale = Locale.getDefault();
/*
* Set new default time zone and locale,
* which will be used during the tests execution.
*/
TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_TIME_ZONE_ID));
Locale.setDefault(Locale.US);
}
@Before
public void setUp() {
VolatileStorageService volatileStorageService = new VolatileStorageService();
registerService(volatileStorageService);
managedThingProvider = getService(ThingProvider.class, ManagedThingProvider.class);
assertThat("Could not get ManagedThingProvider", managedThingProvider, is(notNullValue()));
thingRegistry = getService(ThingRegistry.class);
assertThat("Could not get ThingRegistry", thingRegistry, is(notNullValue()));
itemRegistry = getService(ItemRegistry.class);
assertThat("Could not get ItemRegistry", itemRegistry, is(notNullValue()));
}
@After
public void tearDown() {
if (ntpThing != null) {
Thing removedThing = thingRegistry.forceRemove(ntpThing.getUID());
assertThat("The ntp thing was not deleted", removedThing, is(notNullValue()));
}
if (testItem != null) {
itemRegistry.remove(TEST_ITEM_NAME);
}
}
@AfterClass
public static void tearDownClass() {
// Set the default time zone and locale to their initial value.
TimeZone.setDefault(systemTimeZone);
Locale.setDefault(locale);
}
@Test
public void testStringChannelTimeZoneUpdate() {
final String expectedTimeZonePDT = "PDT";
final String expectedTimeZonePST = "PST";
Configuration configuration = new Configuration();
configuration.put(NtpBindingConstants.PROPERTY_TIMEZONE, TEST_TIME_ZONE_ID);
Configuration channelConfig = new Configuration();
/*
* Set the format of the date, so it is updated in the item registry
* in a format from which we can easily get the time zone.
*/
channelConfig.put(NtpBindingConstants.PROPERTY_DATE_TIME_FORMAT, TEST_DATE_TIME_FORMAT);
initialize(configuration, NtpBindingConstants.CHANNEL_STRING, ACCEPTED_ITEM_TYPE_STRING, channelConfig);
String timeZoneFromItemRegistry = getStringChannelTimeZoneFromItemRegistry();
assertThat("The string channel was not updated with the right timezone", timeZoneFromItemRegistry,
is(anyOf(equalTo(expectedTimeZonePDT), equalTo(expectedTimeZonePST))));
}
@Ignore("the dateTime channel is updated with a time from the system timezone")
@Test
public void testDateTimeChannelTimeZoneUpdate() {
final String expectedTimeZone = "-0700";
Configuration configuration = new Configuration();
configuration.put(NtpBindingConstants.PROPERTY_TIMEZONE, TEST_TIME_ZONE_ID);
initialize(configuration, NtpBindingConstants.CHANNEL_DATE_TIME, ACCEPTED_ITEM_TYPE_DATE_TIME, null);
String testItemState = getItemState(ACCEPTED_ITEM_TYPE_DATE_TIME).toString();
/*
* There is no way to format the date in the dateTime channel
* in advance(there is no property for formatting in the dateTime channel),
* so we will rely on the format, returned by the toString() method of the DateTimeType.
*/
// FIXME: Adapt the tests if property for formatting in the dateTime channel is added.
assertFormat(testItemState, DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS);
/*
* Because of the format from the toString() method,
* the time zone will be the last five symbols of
* the string from the item registry(e.g. "+0300" or "-0700").
*/
String timeZoneFromItemRegistry = testItemState.substring(testItemState.length() - expectedTimeZone.length());
assertThat("The dateTime channel was not updated with the right timezone", timeZoneFromItemRegistry,
is(equalTo(expectedTimeZone)));
}
@Ignore("the time zone in the calendar is lost after the serialization of the state")
@Test
public void testDateTimeChannelCalendarTimeZoneUpdate() {
Configuration configuration = new Configuration();
configuration.put(NtpBindingConstants.PROPERTY_TIMEZONE, TEST_TIME_ZONE_ID);
initialize(configuration, NtpBindingConstants.CHANNEL_DATE_TIME, ACCEPTED_ITEM_TYPE_DATE_TIME, null);
String timeZoneIdFromItemRegistry = ((DateTimeType) getItemState(ACCEPTED_ITEM_TYPE_DATE_TIME)).getCalendar()
.getTimeZone().getID();
assertThat("The dateTime channel calendar was not updated with the right timezone", timeZoneIdFromItemRegistry,
is(equalTo(TEST_TIME_ZONE_ID)));
}
@Test
public void testStringChannelDefaultTimeZoneUpdate() {
final String expectedTimeZoneEEST = "EEST";
final String expectedTimeZoneEET = "EET";
Configuration configuration = new Configuration();
Configuration channelConfig = new Configuration();
/*
* Set the format of the date, so it is updated in the item registry
* in a format from which we can easily get the time zone.
*/
channelConfig.put(NtpBindingConstants.PROPERTY_DATE_TIME_FORMAT, TEST_DATE_TIME_FORMAT);
// Initialize with configuration with no time zone property set.
initialize(configuration, NtpBindingConstants.CHANNEL_STRING, ACCEPTED_ITEM_TYPE_STRING, null);
String timeZoneFromItemRegistry = getStringChannelTimeZoneFromItemRegistry();
assertThat("The string channel was not updated with the right timezone", timeZoneFromItemRegistry,
is(anyOf(equalTo(expectedTimeZoneEEST), equalTo(expectedTimeZoneEET))));
}
@Test
public void testDateTimeChannelDefaultTimeZoneUpdate() {
Calendar systemCalendar = Calendar.getInstance();
String expectedTimeZone = getDateTimeChannelTimeZone(new DateTimeType(systemCalendar).toString());
Configuration configuration = new Configuration();
// Initialize with configuration with no time zone property set.
initialize(configuration, NtpBindingConstants.CHANNEL_DATE_TIME, ACCEPTED_ITEM_TYPE_DATE_TIME, null);
String testItemState = getItemState(ACCEPTED_ITEM_TYPE_DATE_TIME).toString();
/*
* There is no way to format the date in the dateTime channel
* in advance(there is no property for formatting in the dateTime channel),
* so we will rely on the format, returned by the toString() method of the DateTimeType.
*/
// FIXME: Adapt the tests if property for formatting in the dateTime channel is added.
assertFormat(testItemState, DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS);
String timeZoneFromItemRegistry = getDateTimeChannelTimeZone(testItemState);
assertThat("The dateTime channel was not updated with the right timezone", timeZoneFromItemRegistry,
is(equalTo(expectedTimeZone)));
}
@Test
public void testDateTimeChannelCalendarDefaultTimeZoneUpdate() {
Configuration configuration = new Configuration();
// Initialize with configuration with no time zone property set.
initialize(configuration, NtpBindingConstants.CHANNEL_DATE_TIME, ACCEPTED_ITEM_TYPE_DATE_TIME, null);
String timeZoneIdFromItemRegistry = ((DateTimeType) getItemState(ACCEPTED_ITEM_TYPE_DATE_TIME)).getCalendar()
.getTimeZone().getID();
assertThat("The dateTime channel calendar was not updated with the right timezone", timeZoneIdFromItemRegistry,
is(equalTo(DEFAULT_TIME_ZONE_ID)));
}
@Test
public void testStringChannelFormatting() {
final String formatPattern = "EEE, d MMM yyyy HH:mm:ss Z";
Configuration configuration = new Configuration();
Configuration channelConfig = new Configuration();
channelConfig.put(NtpBindingConstants.PROPERTY_DATE_TIME_FORMAT, formatPattern);
initialize(configuration, NtpBindingConstants.CHANNEL_STRING, ACCEPTED_ITEM_TYPE_STRING, channelConfig);
String dateFromItemRegistry = getItemState(ACCEPTED_ITEM_TYPE_STRING).toString();
assertFormat(dateFromItemRegistry, formatPattern);
}
@Test
public void testStringChannelDefaultFormatting() {
Configuration configuration = new Configuration();
// Initialize with configuration with no property for formatting set.
initialize(configuration, NtpBindingConstants.CHANNEL_STRING, ACCEPTED_ITEM_TYPE_STRING, null);
String dateFromItemRegistryString = getItemState(ACCEPTED_ITEM_TYPE_STRING).toString();
assertFormat(dateFromItemRegistryString, NtpHandler.DATE_PATTERN_WITH_TZ);
}
@Test
public void testEmptyStringPropertyFormatting() {
Configuration configuration = new Configuration();
Configuration channelConfig = new Configuration();
// Empty string
channelConfig.put(NtpBindingConstants.PROPERTY_DATE_TIME_FORMAT, "");
initialize(configuration, NtpBindingConstants.CHANNEL_STRING, ACCEPTED_ITEM_TYPE_STRING, channelConfig);
String dateFromItemRegistry = getItemState(ACCEPTED_ITEM_TYPE_STRING).toString();
assertFormat(dateFromItemRegistry, NtpHandler.DATE_PATTERN_WITH_TZ);
}
@Test
public void testNullPropertyFormatting() {
Configuration configuration = new Configuration();
Configuration channelConfig = new Configuration();
channelConfig.put(NtpBindingConstants.PROPERTY_DATE_TIME_FORMAT, null);
initialize(configuration, NtpBindingConstants.CHANNEL_STRING, ACCEPTED_ITEM_TYPE_STRING, channelConfig);
String dateFromItemRegistry = getItemState(ACCEPTED_ITEM_TYPE_STRING).toString();
assertFormat(dateFromItemRegistry, NtpHandler.DATE_PATTERN_WITH_TZ);
}
@Test
public void testDateTimeChannelWithUnknownHost() {
assertCommunicationError(ACCEPTED_ITEM_TYPE_DATE_TIME);
}
@Test
public void testStringChannelWithUnknownHost() {
assertCommunicationError(ACCEPTED_ITEM_TYPE_STRING);
}
@Test
public void testStringChannelHandleCommand() {
assertEventIsReceived(UpdateEventType.HANDLE_COMMAND, NtpBindingConstants.CHANNEL_STRING,
ACCEPTED_ITEM_TYPE_STRING);
}
@Test
public void testDateTimeChannelHandleCommand() {
assertEventIsReceived(UpdateEventType.HANDLE_COMMAND, NtpBindingConstants.CHANNEL_DATE_TIME,
ACCEPTED_ITEM_TYPE_DATE_TIME);
}
@Test
public void testStringChannelLinking() {
assertEventIsReceived(UpdateEventType.CHANNEL_LINKED, NtpBindingConstants.CHANNEL_STRING,
ACCEPTED_ITEM_TYPE_STRING);
}
@Test
public void testDateTimeChannelLinking() {
assertEventIsReceived(UpdateEventType.CHANNEL_LINKED, NtpBindingConstants.CHANNEL_DATE_TIME,
ACCEPTED_ITEM_TYPE_DATE_TIME);
}
private void initialize(Configuration configuration, String channelID, String acceptedItemType,
Configuration channelConfiguration) {
ThingUID ntpUid = new ThingUID(NtpBindingConstants.THING_TYPE_NTP, TEST_THING_ID);
ChannelUID channelUID = new ChannelUID(ntpUid, channelID);
Channel channel;
if (channelConfiguration != null) {
channel = new Channel(channelUID, acceptedItemType, channelConfiguration);
} else {
channel = new Channel(channelUID, acceptedItemType);
}
ntpThing = ThingBuilder.create(NtpBindingConstants.THING_TYPE_NTP, ntpUid).withConfiguration(configuration)
.withChannel(channel).build();
managedThingProvider.add(ntpThing);
// Wait for the NTP thing to be added to the ManagedThingProvider.
ntpHandler = waitForAssert(() -> {
final ThingHandler thingHandler = ntpThing.getHandler();
assertThat(thingHandler, is(instanceOf(NtpHandler.class)));
assertThat(ntpThing.getStatus(), is(equalTo(ThingStatus.ONLINE)));
return (NtpHandler) thingHandler;
}, DFL_TIMEOUT * 3, DFL_SLEEP_TIME);
if (acceptedItemType.equals(ACCEPTED_ITEM_TYPE_STRING)) {
testItem = new StringItem(TEST_ITEM_NAME);
} else if (acceptedItemType.equals(ACCEPTED_ITEM_TYPE_DATE_TIME)) {
testItem = new DateTimeItem(TEST_ITEM_NAME);
}
itemRegistry.add(testItem);
// Wait for the item , linked to the NTP thing to be added to the ManagedThingProvider.
final ManagedItemChannelLinkProvider itemChannelLinkProvider = waitForAssert(() -> {
final ManagedItemChannelLinkProvider tmp = getService(ManagedItemChannelLinkProvider.class);
assertThat("Could not get ManagedItemChannelLinkProvider", tmp, is(notNullValue()));
return tmp;
});
itemChannelLinkProvider.add(new ItemChannelLink(TEST_ITEM_NAME, channelUID));
}
private State getItemState(String acceptedItemType) {
final Item testItem = waitForAssert(() -> {
Item tmp;
try {
tmp = itemRegistry.getItem(TEST_ITEM_NAME);
} catch (ItemNotFoundException e) {
tmp = null;
}
assertThat("The item was null", tmp, is(notNullValue()));
return tmp;
});
return waitForAssert(() -> {
final State testItemState = testItem.getState();
if (acceptedItemType.equals(ACCEPTED_ITEM_TYPE_STRING)) {
assertThat("The item was not of type StringType", testItemState, is(instanceOf(StringType.class)));
} else if (acceptedItemType.equals(ACCEPTED_ITEM_TYPE_DATE_TIME)) {
assertThat("The item was not of type DateTimeType", testItemState, is(instanceOf(DateTimeType.class)));
}
return testItemState;
}, 3 * DFL_TIMEOUT, 2 * DFL_SLEEP_TIME);
}
private String getDateTimeChannelTimeZone(String date) {
/*
* Because of the format from the toString() method,
* the time zone will be the last five symbols of
* the string from the item registry(e.g. "+0300" or "-0700").
*/
return date.substring(date.length() - 5);
}
private String getStringChannelTimeZoneFromItemRegistry() {
String itemState = getItemState(ACCEPTED_ITEM_TYPE_STRING).toString();
/*
* This method is used only in tests for the string channel,
* where we have set the format for the date in advance.
* Because of that format, we know that the time zone will be the
* last word of the string from the item registry.
*/
// FIXME: This can happen a lot easier with Java 8 date time API, so tests can be adapted, if there is an
// upgrade to Java 8
String timeZoneFromItemRegistry = StringUtils.substringAfterLast(itemState, " ");
return timeZoneFromItemRegistry;
}
private void assertFormat(String initialDate, String formatPattern) {
SimpleDateFormat dateFormat = new SimpleDateFormat(formatPattern);
final Date date;
try {
date = dateFormat.parse(initialDate);
} catch (ParseException e) {
fail("An exception $e was thrown, while trying to parse the date $initialDate");
throw new IllegalStateException("already failed");
}
String formattedDate = dateFormat.format(date);
assertThat("The default formatting was not used", formattedDate, is(equalTo(initialDate)));
}
private void assertCommunicationError(String acceptedItemType) {
Configuration configuration = new Configuration();
configuration.put(NtpBindingConstants.PROPERTY_NTP_SERVER, "wrong.hostname");
if (acceptedItemType.equals(ACCEPTED_ITEM_TYPE_DATE_TIME)) {
initialize(configuration, NtpBindingConstants.CHANNEL_DATE_TIME, ACCEPTED_ITEM_TYPE_DATE_TIME, null);
} else if (acceptedItemType.equals(ACCEPTED_ITEM_TYPE_STRING)) {
initialize(configuration, NtpBindingConstants.CHANNEL_STRING, ACCEPTED_ITEM_TYPE_STRING, null);
}
waitForAssert(() -> {
assertThat("The thing status was not communication error", ntpThing.getStatusInfo().getStatusDetail(),
is(equalTo(ThingStatusDetail.COMMUNICATION_ERROR)));
});
}
private void assertEventIsReceived(UpdateEventType updateEventType, String channelID, String acceptedItemType) {
Configuration configuration = new Configuration();
initialize(configuration, channelID, acceptedItemType, null);
eventSubscriberMock = new EventSubscriberMock();
registerService(eventSubscriberMock, EventSubscriber.class.getName());
if (updateEventType.equals(UpdateEventType.HANDLE_COMMAND)) {
ntpHandler.handleCommand(null, null);
} else if (updateEventType.equals(UpdateEventType.CHANNEL_LINKED)) {
ntpHandler.channelLinked(null);
}
waitForAssert(() -> {
assertThat("The $channelID channel was not updated on ${updateEventType.getUpdateEventType()} method",
eventSubscriberMock.isEventReceived, is(true));
});
}
private class EventSubscriberMock extends AbstractItemEventSubscriber {
public boolean isEventReceived = false;
@Override
public void receive(Event event) {
isEventReceived = true;
}
}
}