rspec-openhab-scripting 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1f9f1f804d263fe2179ef5680704f14bc2ba476dc4525f014329aa30ea6e01d0
4
+ data.tar.gz: 924e7e3a2cc0f2578ff526d21a9a4050e216c7555004ebf21a7a76eca62a5e9c
5
+ SHA512:
6
+ metadata.gz: 292ebc4f0386a670ee9b02528e13880e9c9801c397ada5bc83cc9193ff5728f056a27b6b780f6b5b8b789970da03fec6fa63fe6bc3420140ceaedba06f909e9e
7
+ data.tar.gz: b8c0d8e5619c50499189bfc1b85fa496d76549c03bbb23cae531e61afbac87a16b9f2d37cf4b6cb5518a55a5557570a248a0525ad4be66a84bcdb84a03b96bbf
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ module Actions
6
+ # redefine these to do nothing so that rules won't fail
7
+ def notify(msg, email: nil) # rubocop:disable Lint/UnusedMethodArgument:
8
+ logger.debug("notify: #{msg}")
9
+ end
10
+
11
+ def say(text, voice: nil, sink: nil, volume: nil) # rubocop:disable Lint/UnusedMethodArgument:
12
+ logger.debug("say: #{text}")
13
+ end
14
+
15
+ def play_sound(filename, sink: nil, volume: nil) # rubocop:disable Lint/UnusedMethodArgument:
16
+ logger.debug("play_sound: #{filename}")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module OpenHAB
6
+ class API
7
+ def initialize(url, token = nil)
8
+ @faraday = Faraday.new(url) do |f|
9
+ f.response :raise_error
10
+ f.response :json
11
+ f.path_prefix = "/rest/"
12
+ f.headers = { "X-OPENHAB-TOKEN" => token } if token
13
+ end
14
+ end
15
+
16
+ def version
17
+ version = root_data.dig("runtimeInfo", "version")
18
+ version = "#{version}-SNAPSHOT" if root_data.dig("runtimeInfo", "buildString")&.start_with?("Build #")
19
+ version
20
+ end
21
+
22
+ def locale
23
+ root_data["locale"]
24
+ end
25
+
26
+ def measurement_system
27
+ root_data["measurementSystem"]
28
+ end
29
+
30
+ def items
31
+ @faraday.get("items", metadata: ".*").body
32
+ end
33
+
34
+ def item(name)
35
+ @faraday.get("items/#{name}").body
36
+ rescue Faraday::ResourceNotFound
37
+ nil
38
+ end
39
+
40
+ def channel_types
41
+ @faraday.get("channel-types").body
42
+ end
43
+
44
+ def thing_types
45
+ @faraday.get("thing-types").body
46
+ end
47
+
48
+ def things
49
+ @faraday.get("things").body
50
+ end
51
+
52
+ def authenticated?
53
+ @faraday.headers.key?("X-OPENHAB-TOKEN")
54
+ end
55
+
56
+ private
57
+
58
+ def root_data
59
+ @root_data ||= @faraday.get.body
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module Core
5
+ class ItemProxy
6
+ @proxies = {}
7
+
8
+ class << self
9
+ # ensure each item only has a single proxy, so that
10
+ # expect(item).to receive(:method) works
11
+ def new(item)
12
+ @proxies.fetch(item.name) do
13
+ @proxies[item.name] = super
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module Core
5
+ class Logger
6
+ class << self
7
+ def log_service
8
+ @log_service = OSGI.service("org.apache.karaf.log.core.LogService")
9
+ end
10
+ end
11
+
12
+ def name
13
+ @sl4fj_logger.name
14
+ end
15
+
16
+ def level
17
+ self.class.log_service.get_level(name)[name]&.downcase&.to_sym
18
+ end
19
+
20
+ def level=(level)
21
+ self.class.log_service.set_level(name, level.to_s)
22
+ end
23
+ end
24
+ end
25
+
26
+ module Log
27
+ class << self
28
+ def root
29
+ logger(org.slf4j.Logger::ROOT_LOGGER_NAME)
30
+ end
31
+
32
+ def events
33
+ logger("openhab.event")
34
+ end
35
+
36
+ def logger(object)
37
+ logger_name = object if object.is_a?(String)
38
+ logger_name ||= logger_name(object)
39
+ @loggers[logger_name] ||= Core::Logger.new(logger_name)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module RSpec
6
+ module OpenHAB
7
+ module Core
8
+ module Mocks
9
+ class BundleInstallSupport < SimpleDelegator
10
+ include org.apache.karaf.features.internal.service.BundleInstallSupport
11
+
12
+ def initialize(parent, karaf_wrapper)
13
+ super(parent)
14
+ @karaf_wrapper = karaf_wrapper
15
+ end
16
+
17
+ def set_bundle_start_level(bundle, _start_level)
18
+ return if @karaf_wrapper.send(:blocked_bundle?, bundle)
19
+
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module RSpec
6
+ module OpenHAB
7
+ module Core
8
+ module Mocks
9
+ class BundleResolver
10
+ include org.openhab.core.util.BundleResolver
11
+ include Singleton
12
+
13
+ def initialize
14
+ @classes = {}
15
+ end
16
+
17
+ def register_class(klass, bundle)
18
+ # ensure we have an individual java class already
19
+ @classes[klass.become_java!] = bundle
20
+ end
21
+
22
+ def resolve_bundle(clazz)
23
+ bundle = @classes[clazz]
24
+ return bundle if bundle
25
+
26
+ org.osgi.framework.FrameworkUtil.get_bundle(clazz)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module RSpec
6
+ module OpenHAB
7
+ module Core
8
+ module Mocks
9
+ # reimplement to not use a thread
10
+ class OSGiEventManager
11
+ attr_reader :logger
12
+
13
+ def initialize(typed_event_factories, typed_event_subscribers)
14
+ @typed_event_factories = typed_event_factories
15
+ @typed_event_subscribers = typed_event_subscribers
16
+ @logger = org.slf4j.LoggerFactory.get_logger("rspec.openhab.core.mocks.event_handler")
17
+ end
18
+
19
+ def handle_event(osgi_event)
20
+ type = osgi_event.get_property("type")
21
+ payload = osgi_event.get_property("payload")
22
+ topic = osgi_event.get_property("topic")
23
+ source = osgi_event.get_property("source")
24
+
25
+ if type.is_a?(String) && payload.is_a?(String) && topic.is_a?(String)
26
+ handle_event_internal(type, payload, topic, source) unless type.empty? || payload.empty? || topic.empty?
27
+ else
28
+ logger.error("The handled OSGi event is invalid. " \
29
+ "Expect properties as string named 'type', 'payload', and 'topic'. " \
30
+ "Received event properties are: #{osgi_event.property_names.inspect}")
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def handle_event_internal(type, payload, topic, source)
37
+ event_factory = @typed_event_factories[type]
38
+ unless event_factory
39
+ logger.debug("Could not find an Event Factory for the event type '#{type}'.")
40
+ return
41
+ end
42
+
43
+ event_subscribers = event_subscribers(type)
44
+ return if event_subscribers.empty?
45
+
46
+ event = create_event(event_factory, type, payload, topic, source)
47
+ return unless event
48
+
49
+ dispatch_event(event_subscribers, event)
50
+ end
51
+
52
+ def event_subscribers(event_type)
53
+ event_type_subscribers = @typed_event_subscribers[event_type]
54
+ all_event_type_subscribers = @typed_event_subscribers["ALL"]
55
+
56
+ subscribers = java.util.HashSet.new
57
+ subscribers.add_all(event_type_subscribers) if event_type_subscribers
58
+ subscribers.add_all(all_event_type_subscribers) if all_event_type_subscribers
59
+ subscribers
60
+ end
61
+
62
+ def create_event(event_factory, type, payload, topic, source)
63
+ event_factory.create_event(type, topic, payload, source)
64
+ rescue Exception => e
65
+ logger.warn("Creation of event failed, because one of the " \
66
+ "registered event factories has thrown an exception: #{e.inspect}")
67
+ nil
68
+ end
69
+
70
+ def dispatch_event(event_subscribers, event)
71
+ event_subscribers.each do |event_subscriber|
72
+ filter = event_subscriber.event_filter
73
+ if filter.nil? || filter.apply(event)
74
+ begin
75
+ event_subscriber.receive(event)
76
+ rescue Exception => e
77
+ logger.warn(
78
+ "Dispatching/filtering event for subscriber '#{event_subscriber.class}' failed: #{e.inspect}"
79
+ )
80
+ end
81
+ else
82
+ logger.trace("Skip event subscriber (#{event_subscriber.class}) because of its filter.")
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ class EventAdmin < org.osgi.util.tracker.ServiceTracker
89
+ include org.osgi.service.event.EventAdmin
90
+
91
+ def initialize(bundle_context)
92
+ super(bundle_context, "org.osgi.service.event.EventHandler", nil)
93
+
94
+ @handlers_matching_all_events = []
95
+ @handlers_matching_topics = Hash.new { |h, k| h[k] = [] }
96
+ open
97
+ end
98
+
99
+ def addingService(reference) # rubocop:disable Naming/MethodName
100
+ topics = Array(reference.get_property(org.osgi.service.event.EventConstants::EVENT_TOPIC))
101
+ topics = nil if topics.empty? || topics.include?("*")
102
+
103
+ service = ::OpenHAB::Core::OSGI.send(:bundle_context).get_service(reference)
104
+
105
+ if reference.get_property("component.name") == "org.openhab.core.internal.events.OSGiEventManager"
106
+ # OSGiEventManager will create a ThreadedEventHandler on OSGi activation;
107
+ # we're skipping that, and directly sending to a non-threaded event handler.
108
+ service.class.field_reader :typedEventFactories, :typedEventSubscribers
109
+ service = OSGiEventManager.new(service.typedEventFactories, service.typedEventSubscribers)
110
+ end
111
+ if topics.nil?
112
+ @handlers_matching_all_events << service
113
+ else
114
+ topics.each do |topic|
115
+ @handlers_matching_topics[topic] << service
116
+ end
117
+ end
118
+ service
119
+ end
120
+
121
+ def postEvent(event) # rubocop:disable Naming/MethodName
122
+ sendEvent(event)
123
+ end
124
+
125
+ def sendEvent(event) # rubocop:disable Naming/MethodName
126
+ # prevent re-entrancy
127
+ if (pending_events = Thread.current[:event_admin_pending_events])
128
+ pending_events << event
129
+ return
130
+ end
131
+
132
+ pending_events = Thread.current[:event_admin_pending_events] = []
133
+ handle_event(event)
134
+ handle_event(pending_events.shift) until pending_events.empty?
135
+ Thread.current[:event_admin_pending_events] = nil
136
+ end
137
+
138
+ private
139
+
140
+ def handle_event(event)
141
+ @handlers_matching_all_events.each { |h| h.handle_event(event) }
142
+ @handlers_matching_topics[event.topic].each { |h| h.handle_event(event) }
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module OpenHAB
5
+ module Core
6
+ module Mocks
7
+ class PersistenceService
8
+ include org.openhab.core.persistence.ModifiablePersistenceService
9
+ include Singleton
10
+
11
+ class HistoricItem
12
+ include org.openhab.core.persistence.HistoricItem
13
+
14
+ attr_reader :timestamp, :state, :name
15
+
16
+ def initialize(timestamp, state, name)
17
+ @timestamp = timestamp
18
+ @state = state
19
+ @name = name
20
+ end
21
+ end
22
+
23
+ attr_reader :id
24
+
25
+ def initialize
26
+ @id = "default"
27
+ reset
28
+ end
29
+
30
+ def reset
31
+ @data = Hash.new { |h, k| h[k] = [] }
32
+ end
33
+
34
+ def store(item, date = nil, state = nil)
35
+ date = nil if date.is_a?(String) # alias overload
36
+ state ||= item.state
37
+ date ||= ZonedDateTime.now
38
+
39
+ new_item = HistoricItem.new(date, state, item.name)
40
+
41
+ item_history = @data[item.name]
42
+
43
+ insert_index = item_history.bsearch_index do |i|
44
+ i.timestamp.compare_to(date).positive?
45
+ end
46
+
47
+ return item_history << new_item unless insert_index
48
+
49
+ return item_history[insert_index].state = state if item_history[insert_index].timestamp == date
50
+
51
+ item_history.insert(insert_index, new_item)
52
+ end
53
+
54
+ def remove(filter)
55
+ query_internal(filter) do |item_history, index|
56
+ historic_item = item_history.delete_at(index)
57
+ @data.delete(historic_item.name) if item_history.empty?
58
+ end
59
+ end
60
+
61
+ def query(filter)
62
+ result = []
63
+
64
+ query_internal(filter) do |item_history, index|
65
+ result << item_history[index]
66
+
67
+ return result if filter.page_number.zero? && result.length == filter.page_size && filter.item_name
68
+ end
69
+
70
+ result.sort_by! { |hi| hi.timestamp.to_instant.to_epoch_milli } unless filter.item_name
71
+
72
+ unless filter.page_number.zero?
73
+ result = result.slice(filter.page_number * filter.page_size, filter.page_size)
74
+ end
75
+
76
+ result
77
+ end
78
+
79
+ def get_item_info # rubocop:disable Naming/AccessorMethodName must match Java interface
80
+ @data.map do |(n, entries)|
81
+ [n, entries.length, entries.first.timestamp, entries.last.timestamp]
82
+ end.to_set
83
+ end
84
+
85
+ def get_default_strategies # rubocop:disable Naming/AccessorMethodName must match Java interface
86
+ [org.openhab.core.persistence.strategy.PersistenceStrategy::Globals::CHANGE]
87
+ end
88
+
89
+ private
90
+
91
+ def query_internal(filter, &block)
92
+ if filter.item_name
93
+ return unless @data.key?(filter.item_name)
94
+
95
+ query_item_internal(@data[filter.item_name], filter, &block)
96
+ else
97
+ @data.each_value do |item_history|
98
+ query_item_internal(item_history, filter, &block)
99
+ end
100
+ end
101
+ end
102
+
103
+ def query_item_internal(item_history, filter)
104
+ first_index = 0
105
+ last_index = item_history.length
106
+
107
+ if filter.begin_date
108
+ first_index = item_history.bsearch_index do |i|
109
+ i.timestamp.compare_to(filter.begin_date).positive?
110
+ end
111
+ return if first_index.nil?
112
+ end
113
+
114
+ if filter.end_date
115
+ last_index = item_history.bsearch_index do |i|
116
+ i.timestamp.compare_to(filter.end_date).positive?
117
+ end
118
+ return if last_index.zero?
119
+
120
+ last_index ||= item_history.length
121
+ end
122
+
123
+ range = first_index...last_index
124
+
125
+ operator = filter.operator.symbol
126
+ operator = "==" if operator == "="
127
+
128
+ block = lambda do |i|
129
+ next if filter.state && !item_history[i].state.send(operator, filter.state)
130
+
131
+ yield(item_history, i)
132
+ end
133
+
134
+ if filter.ordering == filter.class::Ordering::DESCENDING
135
+ range.reverse_each(&block)
136
+ else
137
+ range.each(&block)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module RSpec
6
+ module OpenHAB
7
+ module Core
8
+ module Mocks
9
+ class CallbacksMap < java.util.HashMap
10
+ def put(_rule_uid, trigger_handler)
11
+ trigger_handler.executor.shutdown_now
12
+ trigger_handler.executor = SynchronousExecutor.instance
13
+ super
14
+ end
15
+ end
16
+
17
+ class SynchronousExecutor < java.util.concurrent.ScheduledThreadPoolExecutor
18
+ class << self
19
+ def instance
20
+ @instance ||= new(1)
21
+ end
22
+ end
23
+
24
+ def submit(runnable)
25
+ runnable.respond_to?(:run) ? runnable.run : runnable.call
26
+
27
+ java.util.concurrent.CompletableFuture.completed_future(nil)
28
+ end
29
+
30
+ def execute(runnable)
31
+ runnable.run
32
+ end
33
+
34
+ def shutdown; end
35
+
36
+ def shutdown_now
37
+ []
38
+ end
39
+ end
40
+
41
+ class SynchronousExecutorMap
42
+ include java.util.Map
43
+ include Singleton
44
+
45
+ def get(_key)
46
+ SynchronousExecutor.instance
47
+ end
48
+
49
+ def key_set
50
+ java.util.HashSet.new
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ # rubocop:disable Naming have to follow java interface names
6
+ module RSpec
7
+ module OpenHAB
8
+ module Core
9
+ module Mocks
10
+ class ThingHandler
11
+ include org.openhab.core.thing.binding.BridgeHandler
12
+
13
+ attr_reader :thing, :callback
14
+
15
+ def initialize(thing = nil)
16
+ # have to handle the interface method
17
+ if thing.nil?
18
+ status_info = org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder
19
+ .create(org.openhab.core.thing.ThingStatus::ONLINE).build
20
+ @callback.status_updated(self.thing, status_info)
21
+ return
22
+ end
23
+
24
+ # ruby initializer here
25
+ @thing = thing
26
+ end
27
+
28
+ def thing_updated(thing)
29
+ @thing = thing
30
+ end
31
+
32
+ def handle_command(channel, command); end
33
+
34
+ def set_callback(callback)
35
+ @callback = callback
36
+ end
37
+
38
+ def child_handler_initialized(child_handler, child_thing); end
39
+ def child_handler_disposed(child_handler, child_thing); end
40
+
41
+ def channel_linked(_channel_uid); end
42
+ def channel_unlinked(_channel_uid); end
43
+
44
+ def dispose; end
45
+ end
46
+
47
+ class ThingHandlerFactory < org.openhab.core.thing.binding.BaseThingHandlerFactory
48
+ include Singleton
49
+
50
+ class ComponentContext
51
+ include org.osgi.service.component.ComponentContext
52
+ include Singleton
53
+
54
+ def getBundleContext
55
+ org.osgi.framework.FrameworkUtil.get_bundle(org.openhab.core.thing.Thing).bundle_context
56
+ end
57
+ end
58
+ private_constant :ComponentContext
59
+
60
+ def initialize
61
+ super
62
+ activate(ComponentContext.instance)
63
+ end
64
+
65
+ def supportsThingType(_type)
66
+ true
67
+ end
68
+
69
+ def createHandler(thing)
70
+ ThingHandler.new(thing)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ # rubocop:enable Naming
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openhab/core/services"
4
+
5
+ module OpenHAB
6
+ module Core
7
+ class << self
8
+ def wait_till_openhab_ready; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ module Rules
6
+ module Triggers
7
+ def watch(path, glob: "*", for: %i[created deleted modified], attach: nil); end
8
+ end
9
+ end
10
+ end
11
+ end