Quellcode durchsuchen

Merge branch 'integration_tests' into 1.9.0

Christopher Mullins vor 6 Jahren
Ursprung
Commit
34ae2c0053

+ 2 - 0
test/remote/.gitignore

@@ -0,0 +1,2 @@
+./espmh.env
+./settings.json

+ 1 - 0
test/remote/.rspec

@@ -0,0 +1 @@
+--require spec_helper

+ 1 - 0
test/remote/.ruby-version

@@ -0,0 +1 @@
+2.6.0

+ 7 - 0
test/remote/Gemfile

@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+gem 'mqtt', '~> 0.5'
+gem 'dotenv', '~> 2.6'
+gem 'multipart-post'

+ 17 - 0
test/remote/Gemfile.lock

@@ -0,0 +1,17 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    dotenv (2.6.0)
+    mqtt (0.5.0)
+    multipart-post (2.0.0)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  dotenv (~> 2.6)
+  mqtt (~> 0.5)
+  multipart-post
+
+BUNDLED WITH
+   1.17.2

+ 4 - 0
test/remote/espmh.env

@@ -0,0 +1,4 @@
+ESPMH_HOSTNAME=10.133.8.194
+
+# Used to test states, etc.
+ESPMH_TEST_DEVICE_ID_BASE=0x2200

+ 4 - 0
test/remote/espmh.env.example

@@ -0,0 +1,4 @@
+ESPMH_HOSTNAME=milight-hub-test
+
+# Used to test states, etc.
+ESPMH_TEST_DEVICE_ID_BASE=0x2200

+ 65 - 0
test/remote/lib/api_client.rb

@@ -0,0 +1,65 @@
+require 'json'
+require 'net/http'
+require 'net/http/post/multipart'
+require 'uri'
+
+class ApiClient
+  def initialize(host, base_id)
+    @host = host
+    @current_id = Integer(base_id)
+  end
+
+  def generate_id
+    id = @current_id
+    @current_id += 1
+    id
+  end
+
+  def request(type, path, req_body = nil)
+    uri = URI("http://#{@host}#{path}")
+    Net::HTTP.start(uri.host, uri.port) do |http|
+      req_type = Net::HTTP.const_get(type)
+
+      req = req_type.new(uri)
+      if req_body
+        req['Content-Type'] = 'application/json'
+        req.body = req_body
+      end
+
+      res = http.request(req)
+      res.value
+
+      body = res.body
+
+      if res['content-type'].downcase == 'application/json'
+        body = JSON.parse(body)
+      end
+
+      body
+    end
+  end
+
+  def upload_json(path, file)
+    `curl -s "http://#{@host}#{path}" -X POST -F 'f=@settings.json'`
+  end
+
+  def get(path)
+    request(:Get, path)
+  end
+
+  def put(path, body)
+    request(:Put, path, body)
+  end
+
+  def post(path, body)
+    request(:Post, path, body)
+  end
+
+  def get_state(params = {})
+    get("/gateways/#{params[:id]}/#{params[:type]}/#{params[:group_id]}")
+  end
+
+  def patch_state(state, params = {})
+    put("/gateways/#{params[:id]}/#{params[:type]}/#{params[:group_id]}", state.to_json)
+  end
+end

+ 46 - 0
test/remote/settings.json

@@ -0,0 +1,46 @@
+{
+  "admin_username": "",
+  "admin_password": "",
+  "ce_pin": 16,
+  "csn_pin": 15,
+  "reset_pin": 0,
+  "led_pin": -2,
+  "radio_interface_type": "nRF24",
+  "packet_repeats": 50,
+  "http_repeat_factor": 1,
+  "auto_restart_period": 0,
+  "mqtt_server": "deepthought",
+  "mqtt_username": "sidoh",
+  "mqtt_password": "goldslime",
+  "mqtt_topic_pattern": "milight_test/commands/:device_id/:device_type/:group_id",
+  "mqtt_update_topic_pattern": "milight_test/update/:device_id/:device_type/:group_id",
+  "mqtt_state_topic_pattern": "milight/states/:device_id/:device_type/:group_id",
+  "mqtt_lwt_topic": "milight/lwt",
+  "mqtt_lwt_message": "disconnected",
+  "discovery_port": 0,
+  "listen_repeats": 3,
+  "state_flush_interval": 2000,
+  "mqtt_state_rate_limit": 1000,
+  "packet_repeat_throttle_sensitivity": 0,
+  "packet_repeat_throttle_threshold": 200,
+  "packet_repeat_minimum": 3,
+  "enable_automatic_mode_switching": false,
+  "led_mode_wifi_config": "Fast toggle",
+  "led_mode_wifi_failed": "On",
+  "led_mode_operating": "Off",
+  "led_mode_packet": "Flicker",
+  "led_mode_packet_count": 3,
+  "hostname": "milight-hub-test",
+  "rf24_power_level": "MAX",
+  "device_ids": [
+  ],
+  "group_state_fields": [
+    "status",
+    "level",
+    "color_temp",
+    "bulb_mode",
+    "hue",
+    "saturation",
+    "effect"
+  ]
+}

+ 46 - 0
test/remote/settings.json.example

@@ -0,0 +1,46 @@
+{
+  "admin_username": "",
+  "admin_password": "",
+  "ce_pin": 16,
+  "csn_pin": 15,
+  "reset_pin": 0,
+  "led_pin": -2,
+  "radio_interface_type": "nRF24",
+  "packet_repeats": 50,
+  "http_repeat_factor": 1,
+  "auto_restart_period": 0,
+  "mqtt_server": "",
+  "mqtt_username": "",
+  "mqtt_password": "",
+  "mqtt_topic_pattern": "milight_test/commands/:device_id/:device_type/:group_id",
+  "mqtt_update_topic_pattern": "milight_test/update/:device_id/:device_type/:group_id",
+  "mqtt_state_topic_pattern": "milight/states/:device_id/:device_type/:group_id",
+  "mqtt_lwt_topic": "milight/lwt",
+  "mqtt_lwt_message": "disconnected",
+  "discovery_port": 0,
+  "listen_repeats": 3,
+  "state_flush_interval": 2000,
+  "mqtt_state_rate_limit": 1000,
+  "packet_repeat_throttle_sensitivity": 0,
+  "packet_repeat_throttle_threshold": 200,
+  "packet_repeat_minimum": 3,
+  "enable_automatic_mode_switching": false,
+  "led_mode_wifi_config": "Fast toggle",
+  "led_mode_wifi_failed": "On",
+  "led_mode_operating": "Off",
+  "led_mode_packet": "Flicker",
+  "led_mode_packet_count": 3,
+  "hostname": "milight-hub-test",
+  "rf24_power_level": "MAX",
+  "device_ids": [
+  ],
+  "group_state_fields": [
+    "state",
+    "brightness",
+    "color_temp",
+    "bulb_mode",
+    "hue",
+    "saturation",
+    "effect"
+  ]
+}

+ 33 - 0
test/remote/spec/environment_spec.rb

@@ -0,0 +1,33 @@
+require 'api_client'
+
+RSpec.describe 'Environment' do
+  before(:each) do
+    @host = ENV.fetch('ESPMH_HOSTNAME')
+    @client = ApiClient.new(ENV.fetch('ESPMH_HOSTNAME'), ENV.fetch('ESPMH_TEST_DEVICE_ID_BASE'))
+  end
+
+  context 'environment' do
+    it 'should have a host defined' do
+      expect(@host).to_not be_nil
+    end
+
+    it 'should respond to /about' do
+      response = @client.get('/about')
+
+      expect(response).to_not  be_nil
+      expect(response.keys).to include('version')
+    end
+  end
+
+  context 'client' do
+    it 'should return IDs' do
+      id = @client.generate_id
+
+      expect(@client.generate_id).to equal(id + 1)
+    end
+  end
+
+  it 'needs to have a settings.json file' do
+    expect(File.exists?('settings.json')).to be(true)
+  end
+end

+ 104 - 0
test/remote/spec/spec_helper.rb

@@ -0,0 +1,104 @@
+require 'dotenv'
+
+Dotenv.load('espmh.env')
+
+# This file was generated by the `rspec --init` command. Conventionally, all
+# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
+# The generated `.rspec` file contains `--require spec_helper` which will cause
+# this file to always be loaded, without a need to explicitly require it in any
+# files.
+#
+# Given that it is always loaded, you are encouraged to keep this file as
+# light-weight as possible. Requiring heavyweight dependencies from this file
+# will add to the boot time of your test suite on EVERY test run, even for an
+# individual file that may not need all of that loaded. Instead, consider making
+# a separate helper file that requires the additional dependencies and performs
+# the additional setup, and require it from the spec files that actually need
+# it.
+#
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+  # rspec-expectations config goes here. You can use an alternate
+  # assertion/expectation library such as wrong or the stdlib/minitest
+  # assertions if you prefer.
+  config.expect_with :rspec do |expectations|
+    # This option will default to `true` in RSpec 4. It makes the `description`
+    # and `failure_message` of custom matchers include text for helper methods
+    # defined using `chain`, e.g.:
+    #     be_bigger_than(2).and_smaller_than(4).description
+    #     # => "be bigger than 2 and smaller than 4"
+    # ...rather than:
+    #     # => "be bigger than 2"
+    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+  end
+
+  # rspec-mocks config goes here. You can use an alternate test double
+  # library (such as bogus or mocha) by changing the `mock_with` option here.
+  config.mock_with :rspec do |mocks|
+    # Prevents you from mocking or stubbing a method that does not exist on
+    # a real object. This is generally recommended, and will default to
+    # `true` in RSpec 4.
+    mocks.verify_partial_doubles = true
+  end
+
+  # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
+  # have no way to turn it off -- the option exists only for backwards
+  # compatibility in RSpec 3). It causes shared context metadata to be
+  # inherited by the metadata hash of host groups and examples, rather than
+  # triggering implicit auto-inclusion in groups with matching metadata.
+  config.shared_context_metadata_behavior = :apply_to_host_groups
+
+# The settings below are suggested to provide a good initial experience
+# with RSpec, but feel free to customize to your heart's content.
+=begin
+  # This allows you to limit a spec run to individual examples or groups
+  # you care about by tagging them with `:focus` metadata. When nothing
+  # is tagged with `:focus`, all examples get run. RSpec also provides
+  # aliases for `it`, `describe`, and `context` that include `:focus`
+  # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
+  config.filter_run_when_matching :focus
+
+  # Allows RSpec to persist some state between runs in order to support
+  # the `--only-failures` and `--next-failure` CLI options. We recommend
+  # you configure your source control system to ignore this file.
+  config.example_status_persistence_file_path = "spec/examples.txt"
+
+  # Limits the available syntax to the non-monkey patched syntax that is
+  # recommended. For more details, see:
+  #   - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
+  #   - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+  #   - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
+  config.disable_monkey_patching!
+
+  # This setting enables warnings. It's recommended, but in some cases may
+  # be too noisy due to issues in dependencies.
+  config.warnings = true
+
+  # Many RSpec users commonly either run the entire suite or an individual
+  # file, and it's useful to allow more verbose output when running an
+  # individual spec file.
+  if config.files_to_run.one?
+    # Use the documentation formatter for detailed output,
+    # unless a formatter has already been configured
+    # (e.g. via a command-line flag).
+    config.default_formatter = "doc"
+  end
+
+  # Print the 10 slowest examples and example groups at the
+  # end of the spec run, to help surface which specs are running
+  # particularly slow.
+  config.profile_examples = 10
+
+  # Run specs in random order to surface order dependencies. If you find an
+  # order dependency and want to debug it, you can fix the order by providing
+  # the seed, which is printed after each run.
+  #     --seed 1234
+  config.order = :random
+
+  # Seed global randomization in this process using the `--seed` CLI option.
+  # Setting this allows you to use `--seed` to deterministically reproduce
+  # test failures related to randomization by passing the same `--seed` value
+  # as the one that triggered the failure.
+  Kernel.srand config.seed
+=end
+end

+ 108 - 0
test/remote/spec/state_spec.rb

@@ -0,0 +1,108 @@
+require 'api_client'
+
+RSpec.describe 'State' do
+  before(:all) do
+    @client = ApiClient.new(ENV.fetch('ESPMH_HOSTNAME'), ENV.fetch('ESPMH_TEST_DEVICE_ID_BASE'))
+    @client.upload_json('/settings', 'settings.json')
+  end
+
+  before(:each) do
+    @id_params = {
+      id: @client.generate_id,
+      type: 'rgb_cct',
+      group_id: 1
+    }
+  end
+
+  context 'toggle command' do
+    it 'should toggle ON to OFF' do
+      init_state = @client.patch_state({'status' => 'ON'}, @id_params)
+      expect(init_state['status']).to eq('ON')
+
+      next_state = @client.patch_state({'command' => 'toggle'}, @id_params)
+      expect(next_state['status']).to eq('OFF')
+    end
+
+    it 'should toggle OFF to ON' do
+      init_state = @client.patch_state({'status' => 'OFF'}, @id_params)
+      expect(init_state['status']).to eq('OFF')
+
+      next_state = @client.patch_state({'command' => 'toggle'}, @id_params)
+      expect(next_state['status']).to eq('ON')
+    end
+  end
+
+  context 'persistence' do
+    it 'should persist parameters' do
+      desired_state = {
+        'status' => 'ON',
+        'level' => 100,
+        'hue' => 0,
+        'saturation' => 100
+      }
+      @client.patch_state(desired_state, @id_params)
+      patched_state = @client.get_state(@id_params)
+
+      expect(patched_state.keys).to include(*desired_state.keys)
+      expect(patched_state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
+
+      desired_state = {
+        'status' => 'ON',
+        'level' => 10,
+        'hue' => 49,
+        'saturation' => 20
+      }
+      @client.patch_state(desired_state, @id_params)
+      patched_state = @client.get_state(@id_params)
+
+      expect(patched_state.keys).to include(*desired_state.keys)
+      expect(patched_state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
+    end
+
+    it 'should affect member groups when changing group 0' do
+      group_0_params = @id_params.merge(group_id: 0)
+
+      desired_state = {
+        'status' => 'ON',
+        'level' => 100,
+        'hue' => 0,
+        'saturation' => 100
+      }
+
+      @client.patch_state(desired_state, group_0_params)
+
+      individual_state = desired_state.merge('level' => 10)
+      patched_state = @client.patch_state(individual_state, @id_params)
+
+      expect(patched_state).to_not eq(desired_state)
+      expect(patched_state.keys).to include(*individual_state.keys)
+      expect(patched_state.select { |x| individual_state.include?(x) } ).to eq(individual_state)
+
+      group_4_state = @client.get_state(group_0_params.merge(group_id: 4))
+
+      expect(group_4_state.keys).to include(*desired_state.keys)
+      expect(group_4_state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
+
+      @client.patch_state(desired_state, group_0_params)
+      group_1_state = @client.get_state(group_0_params.merge(group_id: 1))
+
+      expect(group_1_state.keys).to include(*desired_state.keys)
+      expect(group_1_state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
+    end
+
+    # it 'should keep group 0 state' do
+    #   group_0_params = @id_params.merge(group_id: 0)
+    #   desired_state = {
+    #     'status' => 'ON',
+    #     'level' => 100,
+    #     'hue' => 0,
+    #     'saturation' => 100
+    #   }
+
+    #   patched_state = @client.patch_state(desired_state, group_0_params)
+
+    #   expect(patched_state.keys).to include(*desired_state.keys)
+    #   expect(patched_state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
+    # end
+  end
+end