Skip to content

chore: First integration tests for Hue #1541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions drivers/SmartThings/philips-hue/capabilities/hueSyncMode.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
id: samsungim.hueSyncMode
version: 1
status: proposed
name: Hue Sync Mode
ephemeral: false
attributes:
mode:
schema:
type: object
properties:
value:
type: string
title: Hue Sync mode
enum:
- normal
- streaming
additionalProperties: false
required:
- value
enumCommands: []
commands: {}
4 changes: 2 additions & 2 deletions drivers/SmartThings/philips-hue/src/disco/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ function HueDiscovery.search_bridge_for_supported_devices(driver, bridge_network
if type(log_prefix) == "string" and #log_prefix > 0 then prefix = log_prefix .. " " end

local devices, err, _ = api_instance:get_devices()
if err ~= nil or not devices then
if type(err) == "string" or not devices then
log.error_with({ hub_logs = true },
prefix .. "Error querying bridge for devices: " .. (err or "unexpected nil in error position"))
return
Expand Down Expand Up @@ -359,7 +359,7 @@ function HueDiscovery.do_mdns_scan(driver)
local bridge_netinfo = driver.datastore.bridge_netinfo
local mdns_responses, err = mdns.discover(HueDiscovery.ServiceType, HueDiscovery.Domain)

if err ~= nil then
if not mdns_responses and err ~= nil then
log.error_with({ hub_logs = true }, "Error during service discovery: ", err)
return
end
Expand Down
2 changes: 1 addition & 1 deletion drivers/SmartThings/philips-hue/src/disco/light.lua
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ local function handle_simple_light(
device_service_info, bridge_network_id, cache, st_metadata_callback
)
local light_resource, err, _ = api_instance:get_light_by_id(resource_id)
if err ~= nil or not light_resource then
if type(err) == "string" or not light_resource then
log.error_with({ hub_logs = true },
string.format("Error getting light info for %s: %s", device_service_info.product_data.product_name,
(err or "unexpected nil in error position")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ local function _refresh_zigbee(device, hue_api, zigbee_status)
local zigbee_resource_id
if not zigbee_status then
local rest_resp, rest_err = hue_api:get_device_by_id(hue_device_id)
if rest_err ~= nil then
if not rest_resp and rest_err ~= nil then
log.error_with({ hub_logs = true }, rest_err)
return
end
Expand All @@ -43,7 +43,7 @@ local function _refresh_zigbee(device, hue_api, zigbee_status)

if zigbee_resource_id ~= nil then
rest_resp, rest_err = hue_api:get_zigbee_connectivity_by_id(zigbee_resource_id)
if rest_err ~= nil then
if not rest_resp and rest_err ~= nil then
log.error_with({ hub_logs = true }, rest_err)
return
end
Expand Down Expand Up @@ -326,7 +326,7 @@ function RefreshHandlers.do_refresh_light(driver, light_device, light_status_cac
count = count + 1
if do_light_request and light_device:get_field(Fields.IS_ONLINE) then
rest_resp, rest_err = hue_api:get_light_by_id(light_resource_id)
if rest_err ~= nil then
if not rest_resp and rest_err ~= nil then
log.error_with({ hub_logs = true }, rest_err)
goto continue
end
Expand Down
10 changes: 5 additions & 5 deletions drivers/SmartThings/philips-hue/src/hue/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ function PhilipsHueApi.new_bridge_manager(base_url, api_key, socket_builder)
"Creating new Bridge Manager:",
true
))
local control_tx, control_rx = channel.new()
local control_tx, control_rx = channel.new(st_utils.stringify_table(base_url) .. '_control')
control_rx:settimeout(30)
local self = setmetatable(
{
Expand Down Expand Up @@ -209,7 +209,7 @@ end
---@return table|nil response REST response, nil if error
---@return nil|string error nil on success
local function do_get(instance, path)
local reply_tx, reply_rx = channel.new()
local reply_tx, reply_rx = channel.new(path .. '_do_get_reply_chan')
reply_rx:settimeout(10)
local msg = ControlMessageBuilders.Get(path, reply_tx);
try_send(instance, msg)
Expand All @@ -227,7 +227,7 @@ end
---@return table|nil response REST response, nil if error
---@return nil|string error nil on success
local function do_put(instance, path, payload)
local reply_tx, reply_rx = channel.new()
local reply_tx, reply_rx = channel.new(path .. '_do_put_reply_chan')
reply_rx:settimeout(10)
local msg = ControlMessageBuilders.Put(path, payload, reply_tx);
try_send(instance, msg)
Expand All @@ -245,7 +245,7 @@ end
---@return nil|string error nil on success
---@return nil|string partial partial response if available, nil otherwise
function PhilipsHueApi.get_bridge_info(bridge_ip, socket_builder)
local tx, rx = channel.new()
local tx, rx = channel.new('get_bridge_info')
rx:settimeout(10)
cosock.spawn(
function()
Expand All @@ -267,7 +267,7 @@ end
---@return string? error nil on success
---@return string? partial partial response if available, nil otherwise
function PhilipsHueApi.request_api_key(bridge_ip, socket_builder)
local tx, rx = channel.new()
local tx, rx = channel.new("request_api_key")
rx:settimeout(10)
cosock.spawn(
function()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ end
--- Spawn the stray device resolution task, returning a handle to the tx side of the
--- channel for controlling it.
function StrayDeviceHelper.spawn()
local stray_device_tx, stray_device_rx = cosock.channel.new()
local stray_device_tx, stray_device_rx = cosock.channel.new("stray_device")
stray_device_rx:settimeout(30)

cosock.spawn(function()
Expand Down
69 changes: 69 additions & 0 deletions drivers/SmartThings/philips-hue/src/test/helpers/hue_bridge.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
local net_helpers = require "test.helpers.net"
---@class test.helpers.hue_bridge
local m = {}

---Generate a random Hue Application Key/"username"
---@param len number? Number between 10 and 40 for the desired length of the application key. Selected randomly from the range if omitted.
---@return string key the hue application key
function m.random_hue_bridge_key(len)
local charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_"
len = (type(len) == "number" and len > 10 and len) or math.random(10, 40)

local key = ""
for i = 1, len do
local idx = math.random(1, #charset)
local random_char = charset:sub(idx, idx)
key = key .. random_char
end
return key
end

---@class MockHueBridgeInfo
--- @field public name string?
--- @field public mac_addr string?
--- @field public datastoreversion string?
--- @field public swversion string?
--- @field public apiversion string?
--- @field public factorynew boolean?
--- @field public replacesbridgeid string?
--- @field public modelid string?
--- @field public starterkitid string?
--- @field public ip string?

---Generate partiall random Hue Bridge Info
---@param override_info MockHueBridgeInfo? Optional, overrides for the generated fields; can be a partial table.
---@return HueBridgeInfo bridge_info
function m.random_bridge_info(override_info)
override_info = override_info or {}
local mac_addr = override_info.mac_addr or net_helpers.random_mac_address(':', false)

return {
name = override_info.name or "Philips Hue",
datastoreversion = override_info.datastoreversion or "166",
swversion = override_info.swversion or "1963089030",
apiversion = override_info.apiversion or "1.63.0",
mac = mac_addr,
bridge_id = mac_addr:upper():gsub(':', ''),
factorynew = override_info.factorynew or false,
replacesbridgeid = override_info.replacesbridgeid or false,
modelid = override_info.modelid or "BSB002",
starterkitid = override_info.starterkitid or "",
ip = override_info.ip or net_helpers.random_private_ip_address()
}
end

---Asserts values in the expected MockHueBridgeInfo against the actual
---HueBridgeInfo returned from the REST API.
---@param expected HueBridgeInfo
---@param actual HueBridgeInfo
function m.assert_bridge_info(expected, actual)
for k, v in pairs(expected) do
local err_str = string.format(
"Expected bridge info [%s] with value [%s], received [%s]",
k, v, actual[k]
)
assert(v == actual[k], err_str)
end
end

return m
64 changes: 64 additions & 0 deletions drivers/SmartThings/philips-hue/src/test/helpers/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
local Path = require "path".Path

---@class test.helpers
local m = {
socket = require "test.helpers.socket",
net = require "test.helpers.net",
hue_bridge = require "test.helpers.hue_bridge"
}

---Given a relative path in a `Path` table, convert to absolute
---@param path_table Path
---@return Path
function m.convert_relative_path_table_to_absolute(path_table)
local caller_path = Path(assert(arg[0]))
local test_dir_path = assert(
caller_path:get_dir_pos("src") and caller_path:to_dir("src"),
"Couldn't figure out location of test directory"
)
local ret = test_dir_path:append("test"):append(path_table:to_string())
return ret
end

---Given a path string or `Path`, convert it to an absolute path
---@param filepath string|Path
---@return Path
function m.to_absolute_path(filepath)
assert(
type(filepath) == "string" or
(type(filepath) == "table" and filepath.init == Path.init),
"bad argument #1 to 'load_test_data_json_file (string or Path table expected)"
)
local path_table
if type(filepath) == "string" then
path_table = Path(filepath)
else
path_table = filepath
end
if path_table._is_abs then
return path_table
else
return m.convert_relative_path_table_to_absolute(path_table)
end
end

---Load a JSON file to a table. If `json_file` is a string, and it is a relative path,
---it should be relative to the driver's `./src/test` directory.
---
---For example, if you have a JSON file at `./src/test/test_data/foo.json`, you should use
---`test_data/foo.json` as the parameter.
---
---This function also accepts absolute paths and `Path` tables.
---@see Path
---@param json_file string|Path path to the data file
function m.load_test_data_json_file(json_file)
local abs_json_file_path = m.to_absolute_path(json_file)
local file = assert(io.open(abs_json_file_path:to_string()))
local contents = assert(file:read("a"))
assert(file:close())

local json = require "st.json"
return assert(json.decode(contents))
end

return m
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---@class spec_utils
local test_helpers = {}
---@class test.helpers.net
local m = {}

local private_cidr_strings = {
["192.168.0.0/16"] = true,
Expand All @@ -10,7 +10,7 @@ local private_cidr_strings = {
---Generate a random IP address for the given CIDR string
---@param cidr_string string? Optional, must be a CIDR string identifying one of the three private network spaces. Defaults to "192.168.0.0/16"
---@return string ip a random IP address.
function test_helpers.random_private_ip_address(cidr_string)
function m.random_private_ip_address(cidr_string)
cidr_string = cidr_string or "192.168.0.0/16"

local valid_strings = {}
Expand Down Expand Up @@ -52,7 +52,7 @@ end
---@param separator string? Optional, separator to use in between MAC address segments. Can be '.', ':', '-', or ''. Defaults to ':'
---@param cisco_format boolean? if true, use 3 segments of 4 hex characters instead of 6 segments of 2 characters.
---@return string mac Random MAC address.
function test_helpers.random_mac_address(separator, cisco_format)
function m.random_mac_address(separator, cisco_format)
separator = separator or ':'
assert(
separator == '.' or
Expand Down Expand Up @@ -94,40 +94,4 @@ function test_helpers.random_mac_address(separator, cisco_format)
end


---Generate a random Hue Application Key/"username"
---@param len number? Number between 10 and 40 for the desired length of the application key. Selected randomly from the range if omitted.
---@return string key the hue application key
function test_helpers.random_hue_bridge_key(len)
local charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_"
len = (type(len) == "number" and len > 10 and len) or math.random(10, 40)

local key = ""
for i=1,len do
key = key .. charset:sub(math.random(1, #charset), 1)
end
return key
end

---Generate partiall random Hue Bridge Info
---@param override_info HueBridgeInfo? Optional, overrides for the generated fields; can be a partial table.
---@return HueBridgeInfo bridge_info
function test_helpers.random_bridge_info(override_info)
override_info = override_info or {}
local mac_addr = override_info.mac_addr or test_helpers.random_mac_address(':', false)

return {
name = override_info.name or "Philips Hue",
datastoreversion = override_info.datastoreversion or "166",
swversion = override_info.swversion or "1963089030",
apiversion = override_info.apiversion or "1.63.0",
mac = mac_addr,
bridge_id = mac_addr:upper():gsub(':', ''),
factorynew = override_info.factoryenw or false,
replacesbridgeid = override_info.replacesbridgeid or false,
modelid = override_info.modelid or "BSB002",
starterkitid = override_info.starterkitid or "",
ip = override_info.ip or test_helpers.random_private_ip_address()
}
end

return test_helpers
return m
Loading
Loading