rspec-openhab-scripting 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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