diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 00000000..6acd5a93 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,21 @@ +name: hcloud-ruby integration tests +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ruby-version: [ '3.1' ] + steps: + - uses: actions/checkout@v2 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + - name: Run integration tests + run: bundle exec rspec -t integration + env: + HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }} diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index d5dcfe10..99eb75a0 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -28,5 +28,3 @@ jobs: bundler-cache: true - name: Run double tests run: bundle exec rspec -t doubles --order rand - - name: Run legacy tests - run: LEGACY_TESTS=y bundle exec rspec -t ~doubles diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bee17919..29c7a740 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,13 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 3 -Lint/ShadowingOuterLocalVariable: - Exclude: - - 'spec/fake_service/datacenter.rb' - - 'spec/fake_service/location.rb' - - 'spec/fake_service/server_type.rb' - # Offense count: 1 # Configuration parameters: AllowKeywordBlockArguments. Lint/UnderscorePrefixedVariableName: @@ -42,4 +35,3 @@ Metrics/PerceivedComplexity: Naming/MethodParameterName: Exclude: - 'lib/hcloud/abstract_resource.rb' - - 'spec/fake_service/action.rb' diff --git a/spec/fake_service/action.rb b/spec/fake_service/action.rb deleted file mode 100644 index 2959545b..00000000 --- a/spec/fake_service/action.rb +++ /dev/null @@ -1,94 +0,0 @@ -# frozen_string_literal: true - -require 'time' - -module Hcloud - module FakeService - $ACTION_ID = 0 - $ACTIONS = { - 'actions' => [], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class Action < Grape::API - class << self - def add(h = {}) - a = { - id: $ACTION_ID += 1, - progress: 0, - started: Time.now.iso8601, - finished: nil, - error: nil - }.merge(h).deep_stringify_keys - $ACTIONS['actions'] << a - a - end - - def reset - $ACTION_ID = 0 - $ACTIONS['actions'].clear - end - - def resource_actions(resource_type, resource_id) - actions = $ACTIONS.deep_dup - actions['actions'].select! do |action| - action['resources'].to_a.any? do |res| - (res.to_h['type'] == resource_type) && (res.to_h['id'].to_s == resource_id.to_s) - end - end - actions - end - - def resource_action(resource_type, resource_id, action_id) - actions = resource_actions(resource_type, resource_id) - - actions['actions'].find do |action| - action['id'].to_s == action_id.to_s - end - end - end - group :actions do - params do - requires :id, type: Integer - end - route_param :id do - before_validation do - @x = $ACTIONS['actions'].find { |x| x['id'].to_s == params[:id].to_s } - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { action: @x } - end - end - - params do - optional :status, type: String - optional :per_page, type: Integer - optional :page, type: Integer - optional :sort, type: String - end - get do - dc = $ACTIONS.deep_dup - unless params[:status].nil? - dc['actions'].select! { |x| x['status'].to_s == params[:status].to_s } - end - dc['actions'].shuffle! - unless params[:sort].nil? - dc['actions'].sort_by! { |x| x[params[:sort].split(':')[0]] } - dc['actions'].reverse! if params[:sort].end_with?(':desc') - end - FakeService.pagination_wrapper(dc, 'actions', params[:per_page], params[:page]) - end - end - end - end -end diff --git a/spec/fake_service/base.rb b/spec/fake_service/base.rb deleted file mode 100644 index 04bb4a88..00000000 --- a/spec/fake_service/base.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -require 'grape' - -module Hcloud - module FakeService - # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity - def self.pagination_wrapper(object, key, per_page, page) - o = object.deep_dup - per_page ||= 25 - page ||= 1 - per_page = 50 if per_page > 50 - per_page = 25 if per_page < 1 - page = 1 if page < 1 - low = per_page * (page - 1) - high = per_page * page - last_page = (o[key].size / per_page) + ((o[key].size % per_page).zero? ? 0 : 1) - o['meta'] ||= {} - o['meta']['pagination'] = { - 'page' => page, - 'per_page' => per_page, - 'previous_page' => page > 1 ? page - 1 : nil, - 'next_page' => page < last_page ? page + 1 : nil, - 'last_page' => last_page, - 'total_entries' => o[key].size - } - o[key] = o[key][low...high].to_a - o - end - # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity - - def self.label_selector_matches(label_selector, resource_labels) - resource_labels ||= {} - - # only implements a subset of the label selector query language - # to support selectors like "key=value,key2" - selectors = label_selector.split(',') - selectors.all? do |selector| - key, value = selector.split('=', 2) - value = '' if value.nil? # API uses "" to denote labels without values - - resource_labels.key?(key) && resource_labels[key] == value - end - end - - class Base < Grape::API - version 'v1', using: :path - - format :json - - before do - next if headers['Authorization'] == 'Bearer secure' - - error!('Unauthorized', 401) - end - - require_relative './action' - require_relative './server' - require_relative './image' - require_relative './iso' - require_relative './server_type' - require_relative './floating_ip' - require_relative './ssh_key' - require_relative './location' - require_relative './datacenter' - require_relative './firewall' - require_relative './network' - - mount Action - mount Server - mount Image - mount ISO - mount ServerType - mount FloatingIP - mount Datacenter - mount Location - mount SSHKey - mount Firewall - mount Network - end - end -end diff --git a/spec/fake_service/datacenter.rb b/spec/fake_service/datacenter.rb deleted file mode 100644 index 590555e0..00000000 --- a/spec/fake_service/datacenter.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $DATACENTERS = { - 'datacenters' => [ - { - 'id' => 1, - 'name' => 'fsn1-dc8', - 'description' => 'Falkenstein 1 DC 8', - 'location' => { - 'id' => 1, - 'name' => 'fsn1', - 'description' => 'Falkenstein DC Park 1', - 'country' => 'DE', - 'city' => 'Falkenstein', - 'latitude' => 50.47612, - 'longitude' => 12.370071 - }, - 'server_types' => { - 'supported' => [2, 4, 6, 8, 10, 9, 7, 5, 3, 1], - 'available' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - } - }, { - 'id' => 2, - 'name' => 'nbg1-dc3', - 'description' => 'Nuremberg 1 DC 3', - 'location' => { - 'id' => 2, - 'name' => 'nbg1', - 'description' => 'Nuremberg DC Park 1', - 'country' => 'DE', - 'city' => 'Nuremberg', - 'latitude' => 49.452102, - 'longitude' => 11.076665 - }, - 'server_types' => { - 'supported' => [2, 4, 6, 8, 10, 9, 7, 5, 3, 1], - 'available' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - } - } - ], - 'recommendation' => 2, - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class Datacenter < Grape::API - group :datacenters do - params do - requires :id, type: Integer - end - route_param :id do - get do - x = $DATACENTERS['datacenters'].find { |x| x['id'] == params[:id] } - error!({ error: { code: :not_found } }, 404) if x.nil? - { datacenter: x } - end - end - - params do - optional :name, type: String - end - get do - if params.key?(:name) - if params[:name].to_s.include?('-') - dc = $DATACENTERS.deep_dup - dc['datacenters'].select! { |x| x['name'] == params[:name] } - dc - else - error!({ error: { code: :invalid_input } }, 400) - end - else - $DATACENTERS - end - end - end - end - end -end diff --git a/spec/fake_service/firewall.rb b/spec/fake_service/firewall.rb deleted file mode 100644 index 7ce9bd67..00000000 --- a/spec/fake_service/firewall.rb +++ /dev/null @@ -1,185 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $FIREWALL_ID = 0 - $FIREWALLS = { - 'firewalls' => [], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class Firewall < Grape::API - group :firewalls do - params do - requires :id, type: Integer - end - route_param :id do - before_validation do - @x = $FIREWALLS['firewalls'].find { |x| x['id'].to_s == params[:id].to_s } - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { firewall: @x } - end - - group :actions do - params do - requires :aid, type: Integer - end - route_param :aid do - before_validation do - @a = Action.resource_action('firewall', @x['id'], params[:aid]) - error!({ error: { code: :not_found } }, 404) if @a.nil? - end - get do - { action: @a } - end - end - - params do - optional :status, type: String - optional :sort, type: String - end - get do - actions = Action.resource_actions('firewall', @x['id']) - unless params[:status].nil? - actions['actions'].select! do |x| - x['status'].to_s == params[:status].to_s - end - end - actions - end - - params do - optional :rules, type: Array - end - post :set_rules do - error!({ error: { code: :invalid_input } }, 400) if params[:rules].nil? - - @x['rules'] = params[:rules] - - a = Action.add(command: 'set_rules', status: 'success', - resources: [{ id: @x['id'], type: 'firewall' }]) - { actions: [a] } - end - - params do - optional :apply_to, type: Array - end - post :apply_to_resources do - error!({ error: { code: :invalid_input } }, 400) if params[:apply_to].nil? - - @x['applied_to'].concat(params[:apply_to].map(&:to_h)) - - a = Action.add(command: 'apply_to_resources', status: 'success', - resources: [{ id: @x['id'], type: 'firewall' }]) - { actions: [a] } - end - - params do - optional :remove_from, type: Array - end - post :remove_from_resources do - error!({ error: { code: :invalid_input } }, 400) if params[:remove_from].nil? - - params[:remove_from].each do |remove_from| - @x['applied_to'].delete_if do |applied_to| - next unless remove_from['type'] == applied_to['type'] - - case applied_to['type'] - when 'server' - remove_from.dig('server', 'id') == applied_to.dig('server', 'id') - when 'label_selector' - remove_from.dig('label_selector', 'selector') \ - == applied_to.dig('label_selector', 'selector') - end - end - end - - a = Action.add(command: 'remove_from_resources', status: 'success', - resources: [{ id: @x['id'], type: 'firewall' }]) - { actions: [a] } - end - end - - params do - optional :name, type: String - end - put do - @x['name'] = params[:name] unless params[:name].nil? - @x['labels'] = params[:labels] unless params[:labels].nil? - { firewall: @x } - end - - delete do - $FIREWALLS['firewalls'].delete(@x) - @body = nil - status 204 - end - end - - params do - optional :name, type: String - optional :rules, type: Array - optional :apply_to, type: Array - end - post do - error!({ error: { code: :invalid_input } }, 400) if params[:name].nil? - - if $FIREWALLS['firewalls'].any? { |x| params[:name] == x['name'] } - error!({ error: { code: :uniqueness_error } }, 400) - end - - firewall = { - 'id' => $FIREWALL_ID += 1, - 'name' => params[:name], - 'applied_to' => params[:apply_to] || [], - 'rules' => params[:rules] || [], - 'labels' => params[:labels] || {} - } - - actions = [] - unless params[:rules].to_a.empty? - actions << Action.add(command: 'set_rules', status: 'running', - resources: [{ id: firewall['id'], type: 'firewall' }]) - end - unless params[:apply_to].to_a.empty? - actions << Action.add(command: 'apply_to_resources', status: 'running', - resources: [{ id: firewall['id'], type: 'firewall' }]) - end - - $FIREWALLS['firewalls'] << firewall - { actions: actions, firewall: firewall } - end - - params do - optional :name, type: String - end - get do - firewalls = $FIREWALLS.deep_dup - - unless params[:name].nil? - firewalls['firewalls'].select! { |x| x['name'] == params[:name] } - end - - unless params[:label_selector].nil? - firewalls['firewalls'].select! do |x| - FakeService.label_selector_matches(params[:label_selector], x['labels']) - end - end - - firewalls - end - end - end - end -end diff --git a/spec/fake_service/floating_ip.rb b/spec/fake_service/floating_ip.rb deleted file mode 100644 index f5887a25..00000000 --- a/spec/fake_service/floating_ip.rb +++ /dev/null @@ -1,205 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $FLOATING_IPS_IDS = 0 - $FLOATING_IPS = { - 'floating_ips' => [ - { - 'id' => 595, - 'description' => 'moo', - 'ip' => '94.130.188.60', - 'type' => 'ipv4', - 'server' => nil, - 'dns_ptr' => [ - { - 'ip' => '127.0.0.1', - 'dns_ptr' => 'static.1.0.0.127.clients.your-server.de' - } - ], - 'home_location' => { - 'id' => 2, - 'name' => 'nbg1', - 'description' => 'Nuremberg DC Park 1', - 'country' => 'DE', - 'city' => 'Nuremberg', - 'latitude' => 49.452102, - 'longitude' => 11.076665 - }, - 'blocked' => false, - 'created' => '2016-01-30T23:50:00+00:00', - 'protection' => { - 'delete' => false - } - } - ], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 1 - } - } - } - - class FloatingIP < Grape::API - group :floating_ips do - params do - requires :id, type: Integer - end - route_param :id do - before_validation do - @x = $FLOATING_IPS['floating_ips'].find { |x| x['id'].to_s == params[:id].to_s } - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { floating_ip: @x } - end - - params do - optional :description, type: String - end - put do - @x['description'] = params[:description] unless params[:description].nil? - @x['labels'] = params[:labels] unless params[:labels].nil? - { floating_ip: @x } - end - - group :actions do - params do - optional :status, type: String - optional :sort, type: String - end - get do - dc = $ACTIONS.deep_dup - dc['actions'].select! do |x| - x['command'].to_s.include?('floating_ip') && - x['resources'].to_a.any? do |y| - (y.to_h['type'] == 'server') && - (y.to_h['id'].to_s == @x['server'].to_s) - end - end - unless params[:status].nil? - dc['actions'].select! do |x| - x['status'].to_s == params[:status].to_s - end - end - dc - end - - params do - optional :server, type: String - end - post :assign do - a = { 'action' => Action.add(command: 'assign_floating_ip', status: 'success', - resources: [{ id: @x['server'].to_i, type: 'server' }]) } - @x['server'] = params[:server].to_i - a - end - - post :unassign do - a = { 'action' => Action.add(command: 'unassign_floating_ip', status: 'success', - resources: [{ id: @x['server'].to_i, type: 'server' }]) } - @x['server'] = nil - a - end - - params do - requires :ip, type: String - requires :dns_ptr, type: String - end - post :change_dns_ptr do - a = { 'action' => Action.add(command: 'change_dns_ptr', status: 'success', - resources: [{ id: @x['id'].to_i, type: 'floating_ip' }]) } - @x['dns_ptr'].select do |i| - i['dns_ptr'] = params[:dns_ptr] if i['ip'] == params[:ip] - end - a - end - - params do - optional :delete, type: Boolean - end - post :change_protection do - a = { 'action' => Action.add(command: 'change_protection', status: 'success', - resources: [{ id: @x['id'].to_i, type: 'floating_ip' }]) } - @x['protection']['delete'] = params[:delete] unless params[:delete].nil? - a - end - end - - delete do - $FLOATING_IPS['floating_ips'].delete(@x) - @body = nil - status 204 - end - end - - params do - optional :type, type: String - optional :description, type: String - optional :server, type: String - optional :home_location, type: String - end - post do - unless %w[ipv4 ipv6].include?(params[:type]) - error!({ error: { code: :invalid_input } }, 400) - end - if params[:home_location] && !%w[fsn1 nbg1].include?(params[:home_location]) - error!({ error: { code: :invalid_input } }, 400) - end - if params[:server] && !params[:server].to_s[/^\d+$/] - error!({ error: { code: :invalid_input } }, 400) - end - action = nil - if params[:server] - action = Action.add(command: 'assign_floating_ip', status: 'running', - resources: [{ id: params[:server].to_i, type: 'server' }]) - end - params[:home_location] ||= 'nbg1' - f = { - 'id' => $FLOATING_IPS_IDS += 1, - 'description' => params[:description], - 'ip' => '127.0.0.2', - 'type' => params[:type], - 'blocked' => false, - 'server' => params[:server], - 'dns_ptr' => [ - { - 'ip' => '127.0.0.2', - 'dns_ptr' => 'static.2.0.0.127.clients.your-server.de' - } - ], - 'created' => Time.now.to_s, - 'protection' => { - 'delete' => false - }, - 'home_location' => $LOCATIONS['locations'].find { |x| x['name'] == params[:home_location] }, - 'labels' => params[:labels] - } - $FLOATING_IPS['floating_ips'] << f - if params[:server] - { 'action' => action, 'floating_ip' => f } - else - { 'floating_ip' => f } - end - end - - get do - ips = $FLOATING_IPS.deep_dup - ips['floating_ips'].select! { |x| x['name'] == params[:name] } unless params[:name].nil? - unless params[:label_selector].nil? - ips['floating_ips'].select! do |x| - FakeService.label_selector_matches(params[:label_selector], x['labels']) - end - end - - ips - end - end - end - end -end diff --git a/spec/fake_service/image.rb b/spec/fake_service/image.rb deleted file mode 100644 index 97456ba4..00000000 --- a/spec/fake_service/image.rb +++ /dev/null @@ -1,158 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $IMAGES = { - 'images' => [ - { - 'id' => 1, - 'type' => 'system', - 'status' => 'available', - 'name' => 'ubuntu-16.04', - 'description' => 'Ubuntu 16.04', - 'image_size' => nil, - 'disk_size' => 5, - 'created' => '2018-01-15T11:34:45+00:00', - 'created_from' => nil, - 'bound_to' => nil, - 'os_flavor' => 'ubuntu', - 'os_version' => '16.04', - 'rapid_deploy' => true, - 'deprecated' => '2018-02-28T00:00:00+00:00', - 'protection' => { - 'delete' => false - } - }, - { - 'id' => 3454, - 'type' => 'snapshot', - 'status' => 'available', - 'name' => nil, - 'description' => 'snapshot image created at 2018-02-02 10:28:21', - 'image_size' => 0.64352086328125, - 'disk_size' => 20, - 'created' => '2018-02-02T10:28:21+00:00', - 'created_from' => { 'id' => 497_533, 'name' => 'moo5' }, - 'bound_to' => nil, - 'os_flavor' => 'ubuntu', - 'os_version' => nil, - 'rapid_deploy' => false, - 'deprecated' => nil, - 'protection' => { - 'delete' => false - } - } - ], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class Image < Grape::API - group :images do - params do - requires :id, type: Integer - end - route_param :id do - before_validation do - @x = $IMAGES['images'].find { |x| x['id'].to_s == params[:id].to_s } - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { image: @x } - end - - params do - optional :description, type: String - optional :type, type: String - end - put do - if !params[:description].nil? && @x['id'] != 3454 && @x.nil? - error!({ error: { code: :not_found } }, 404) - end - if !params[:type].nil? && @x['id'] != 3454 - if %w[backup system snapshot].include?(params[:type]) - error!({ error: { code: :not_found } }, 400) - else - error!({ error: { code: :invalid_input } }, 400) - end - end - case params[:type] - when 'system' - error!({ error: { code: :service_error } }, 400) - when 'backup' - error!({ error: { code: :service_error } }, 400) - end - @x['description'] = params[:description] unless params[:description].nil? - @x['type'] = params[:type] unless params[:type].nil? - @x['labels'] = params[:labels] unless params[:labels].nil? - { image: @x } - end - - group :actions do - params do - optional :status, type: String - optional :sort, type: String - end - get do - dc = $ACTIONS.deep_dup - dc['actions'].select! do |x| - x['resources'].to_a.any? do |y| - (y.to_h['type'] == 'image') && (y.to_h['id'].to_s == @x['id'].to_s) - end - end - unless params[:status].nil? - dc['actions'].select! do |x| - x['status'].to_s == params[:status].to_s - end - end - dc - end - - params do - optional :delete, type: Boolean - end - post :change_protection do - a = { 'action' => Action.add(command: 'change_protection', status: 'success', - resources: [{ id: @x['id'].to_i, type: 'image' }]) } - @x['protection']['delete'] = params[:delete] unless params[:delete].nil? - a - end - end - - delete do - $IMAGES['images'].delete(@x) - '' - end - end - - params do - optional :name, type: String - optional :type, type: String - optional :bound_to, type: String - end - get do - dc = $IMAGES.deep_dup - dc['images'].select! { |x| x['name'] == params[:name] } if params[:name]&.size&.positive? - dc['images'].select! { |x| x['type'] == params[:type] } if params[:type]&.size&.positive? - unless params[:bound_to].nil? - dc['images'].select! { |x| x['bound_to'].to_s == params[:bound_to].to_s } - end - unless params[:label_selector].nil? - dc['images'].select! do |x| - FakeService.label_selector_matches(params[:label_selector], x['labels']) - end - end - dc - end - end - end - end -end diff --git a/spec/fake_service/iso.rb b/spec/fake_service/iso.rb deleted file mode 100644 index a30daab2..00000000 --- a/spec/fake_service/iso.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $ISOS = { - 'isos' => [ - { - 'id' => 26, - 'name' => 'virtio-win-0.1.141.iso', - 'description' => 'virtio 0.1.141-1', - 'type' => 'public', - 'deprecated' => nil - } - ], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 1 - } - } - } - - class ISO < Grape::API - group :isos do - params do - requires :id, type: Integer - end - route_param :id do - before_validation do - @x = $ISOS['isos'].find { |x| x['id'].to_s == params[:id].to_s } - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { iso: @x } - end - end - - params do - optional :name, type: String - end - get do - dc = $ISOS.deep_dup - dc['isos'].select! { |x| x['name'] == params[:name] } unless params[:name].nil? - dc - end - end - end - end -end diff --git a/spec/fake_service/location.rb b/spec/fake_service/location.rb deleted file mode 100644 index 08a925d4..00000000 --- a/spec/fake_service/location.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $LOCATIONS = { - 'locations' => [ - { - 'id' => 1, - 'name' => 'fsn1', - 'description' => 'Falkenstein DC Park 1', - 'country' => 'DE', - 'city' => 'Falkenstein', - 'latitude' => 50.47612, - 'longitude' => 12.370071 - }, { - 'id' => 2, - 'name' => 'nbg1', - 'description' => 'Nuremberg DC Park 1', - 'country' => 'DE', - 'city' => 'Nuremberg', - 'latitude' => 49.452102, - 'longitude' => 11.076665 - } - ], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class Location < Grape::API - group :locations do - params do - requires :id, type: Integer - end - route_param :id do - get do - x = $LOCATIONS['locations'].find { |x| x['id'] == params[:id] } - error!({ error: { code: :not_found } }, 404) if x.nil? - { location: x } - end - end - - params do - optional :name, type: String - end - get do - if params.key?(:name) - dc = $LOCATIONS.deep_dup - dc['locations'].select! { |x| x['name'] == params[:name] } - dc - else - $LOCATIONS - end - end - end - end - end -end diff --git a/spec/fake_service/network.rb b/spec/fake_service/network.rb deleted file mode 100644 index 113ba751..00000000 --- a/spec/fake_service/network.rb +++ /dev/null @@ -1,190 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $NETWORK_ID = 0 - $NETWORKS = { - 'networks' => [], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class Network < Grape::API - group :networks do - params do - requires :id, type: Integer - end - route_param :id do - before_validation do - @x = $NETWORKS['networks'].find { |x| x['id'].to_s == params[:id].to_s } - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { network: @x } - end - - group :actions do - params do - requires :aid, type: Integer - end - route_param :aid do - before_validation do - @a = Action.resource_action('network', @x['id'], params[:aid]) - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { action: @a } - end - end - - params do - optional :status, type: String - optional :sort, type: String - end - get do - actions = Action.resource_actions('network', @x['id']) - unless params[:status].nil? - actions['actions'].select! do |x| - x['status'].to_s == params[:status].to_s - end - end - actions - end - - post :add_route do - error!({ error: { code: :invalid_input } }, 400) if params[:gateway].nil? - error!({ error: { code: :invalid_input } }, 400) if params[:destination].nil? - - @x['routes'] << { - gateway: params[:gateway], - destination: params[:destination] - } - - a = Action.add(command: 'add_route', status: 'success', - resources: [{ id: @x['id'], type: 'network' }]) - { action: a } - end - - post :delete_route do - error!({ error: { code: :invalid_input } }, 400) if params[:gateway].nil? - error!({ error: { code: :invalid_input } }, 400) if params[:destination].nil? - - @x['routes'].delete_if do |route| - route[:gateway] == params[:gateway] && route[:destination] == params[:destination] - end - - a = Action.add(command: 'delete_route', status: 'success', - resources: [{ id: @x['id'], type: 'network' }]) - { action: a } - end - - post :add_subnet do - error!({ error: { code: :invalid_input } }, 400) if params[:type].nil? - error!({ error: { code: :invalid_input } }, 400) if params[:network_zone].nil? - - @x['subnets'] << { - type: params[:type], - network_zone: params[:network_zone], - # IP range 10.0.0.0/24 might not match the actual sub net - # but for unit tests should be OK. The real API allocates - # a subnet that's inside the network IP range. - ip_range: params[:ip_range] || '10.0.0.0/24' - } - - a = Action.add(command: 'add_subnet', status: 'success', - resources: [{ id: @x['id'], type: 'network' }]) - { action: a } - end - - post :delete_subnet do - error!({ error: { code: :invalid_input } }, 400) if params[:ip_range].nil? - - @x['subnets'].delete_if { |subnet| subnet[:ip_range] == params[:ip_range] } - - a = Action.add(command: 'delete_subnet', status: 'success', - resources: [{ id: @x['id'], type: 'network' }]) - { action: a } - end - end - - params do - optional :name, type: String - end - put do - @x['name'] = params[:name] unless params[:name].nil? - @x['labels'] = params[:labels] unless params[:labels].nil? - { network: @x } - end - - delete do - $NETWORKS['networks'].delete(@x) - @body = nil - status 204 - end - end - - params do - optional :name, type: String - optional :ip_range, type: String - end - post do - error!({ error: { code: :invalid_input } }, 400) if params[:name].nil? - error!({ error: { code: :invalid_input } }, 400) if params[:ip_range].nil? - if $NETWORKS['networks'].any? { |x| params[:name] == x['name'] } - error!({ error: { code: :uniqueness_error } }, 400) - end - - # for tests set the last segment of the net range to 1 to create the gateway - net = IPAddr.new(params[:ip_range]) - masklen = net.ipv6? ? 120 : 24 - gateway = (net.mask(masklen) | 1).to_s - - subnets = {} - unless params[:subnets].nil? - subnets = params[:subnets] - subnets.each do |subnet| - subnet[:gateway] = gateway - subnet[:vswitch_id] = nil - end - end - - routes = params[:routes] || {} - - network = { - 'id' => $NETWORK_ID += 1, - 'name' => params[:name], - 'ip_range' => params[:ip_range], - 'subnets' => subnets, - 'routes' => routes, - 'labels' => params[:labels] - } - $NETWORKS['networks'] << network - { network: network } - end - - params do - optional :name, type: String - end - get do - networks = $NETWORKS.deep_dup - networks['networks'].select! { |x| x['name'] == params[:name] } unless params[:name].nil? - unless params[:label_selector].nil? - networks['networks'].select! do |x| - FakeService.label_selector_matches(params[:label_selector], x['labels']) - end - end - - networks - end - end - end - end -end diff --git a/spec/fake_service/server.rb b/spec/fake_service/server.rb deleted file mode 100644 index 20c63df4..00000000 --- a/spec/fake_service/server.rb +++ /dev/null @@ -1,308 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $SERVER_ID = 0 - $SERVERS = { - 'servers' => [], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class Server < Grape::API - group :servers do - params do - requires :id, type: Integer - end - route_param :id do - before_validation do - @x = $SERVERS['servers'].find { |x| x['id'].to_s == params[:id].to_s } - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { server: @x } - end - - group :actions do - params do - requires :aid, type: Integer - end - route_param :aid do - before_validation do - @a = Action.resource_action('server', @x['id'], params[:aid]) - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { action: @a } - end - end - - params do - optional :status, type: String - optional :sort, type: String - end - get do - actions = Action.resource_actions('server', @x['id']) - unless params[:status].nil? - actions['actions'].select! do |x| - x['status'].to_s == params[:status].to_s - end - end - actions - end - - helpers do - def locked? - a = Action.resource_actions('server', @x['id'])['actions'] - a.to_a.any? { |x| x['status'] == 'running' } - end - end - - post :poweron do - error!({ error: { code: :locked } }, 400) if locked? - a = Action.add(command: 'start_server', status: 'running', - resources: [{ id: @x['id'], type: 'server' }]) - Thread.new do - sleep(0.5) - @x['status'] = 'running' - $ACTIONS['actions'].find { |x| x['id'].to_s == a['id'].to_s }['status'] = 'success' - end - { action: a } - end - - post :poweroff do - error!({ error: { code: :locked } }, 400) if locked? - a = Action.add(command: 'stop_server', status: 'running', - resources: [{ id: @x['id'], type: 'server' }]) - Thread.new do - sleep(0.5) - @x['status'] = 'off' - $ACTIONS['actions'].find { |x| x['id'].to_s == a['id'].to_s }['status'] = 'success' - end - { action: a } - end - - post :reset_password do - error!({ error: { code: :locked } }, 400) if locked? - a = Action.add(command: 'reset_password', status: 'running', - resources: [{ id: @x['id'], type: 'server' }]) - Thread.new do - sleep(0.5) - $ACTIONS['actions'].find { |x| x['id'].to_s == a['id'].to_s }['status'] = 'success' - end - { action: a, root_password: 'test123' } - end - - post :request_console do - error!({ error: { code: :locked } }, 400) if locked? - a = Action.add(command: 'request_console', status: 'running', - resources: [{ id: @x['id'], type: 'server' }]) - Thread.new do - sleep(0.5) - $ACTIONS['actions'].find { |x| x['id'].to_s == a['id'].to_s }['status'] = 'success' - end - { - action: a, - wss_url: "wss://web-console.hetzner.cloud/?server_id=#{@x['id']}&token=token", - password: 'test123' - } - end - - params do - optional :type, type: String - optional :ssh_keys, type: Array[Integer] - end - post :enable_rescue do - t = params[:type] || 'linux64' - unless %w[linux64 linux32 freebsd64].include?(t) - error!({ error: { code: :invalid_input } }, 400) - end - error!({ error: { code: :locked } }, 400) if locked? - a = Action.add(command: 'enable_rescue', status: 'running', - resources: [{ id: @x['id'], type: 'server' }]) - Thread.new do - sleep(0.5) - $ACTIONS['actions'].find { |x| x['id'].to_s == a['id'].to_s }['status'] = 'success' - end - { action: a, root_password: 'test123' } - end - - params do - optional :delete, type: Boolean - optional :rebuild, type: Boolean - end - post :change_protection do - a = { 'action' => Action.add(command: 'change_protection', status: 'running', - resources: [{ id: @x['id'].to_i, type: 'server' }]) } - @x['protection']['delete'] = params[:delete] unless params[:delete].nil? - @x['protection']['rebuild'] = params[:rebuild] unless params[:rebuild].nil? - a - end - - params do - optional :type, type: String - optional :description, type: String - optional :labels, type: Hash - end - post :create_image do - # image will not be stored permanently in this fake, we only return - # it immediately on the API call. But subsequent calls to /images/ endpoints - # will not return the image - a = Action.add(command: 'create_image', status: 'running', - resources: [{ id: @x['id'].to_i, type: 'server' }]) - image = { - 'id' => 1, - 'description' => params[:description], - 'type' => params[:type], - 'labels' => params[:labels] || {} - } - { action: a, image: image } - end - end - - params do - optional :name, type: String - end - put do - if $SERVERS['servers'].any? { |x| x['name'] == params[:name] } - error!({ error: { code: :uniqueness_error } }, 400) - end - @x['name'] = params[:name] unless params[:name].nil? - @x['labels'] = params[:labels] unless params[:labels].nil? - { server: @x } - end - - delete do - $SERVERS['servers'].delete(@x) - { action: Action.add(status: 'success', command: 'delete_server', - resources: [{ id: @x['id'], type: 'server' }]) } - end - end - - params do - optional :name, type: String - optional :server_type, type: String - optional :datacenter, type: String - optional :location, type: String - optional :start_after_create, type: Boolean - optional :image, type: String - optional :ssh_keys, type: Array[Integer] - optional :user_data, type: String - end - post do - server_type = nil - datacenter = $DATACENTERS['datacenters'].first - image = $IMAGES['images'].first - unless params[:name].to_s[/^[A-Za-z0-9-]+$/] - error!({ error: { code: :invalid_input } }, 400) - end - if $SERVERS['servers'].any? { |x| x['name'] == params[:name] } - error!({ error: { code: :uniqueness_error } }, 400) - end - if $SERVER_TYPES['server_types'].none? do |x| - server_type = x if [x['id'].to_s, x['name']].include?(params[:server_type].to_s) - end - error!({ error: { code: :invalid_input, message: 'invalid server_type' } }, 400) - end - if $IMAGES['images'].none? do |x| - image = x if [x['id'].to_s, x['name']].include?(params[:image].to_s) - end - error!({ error: { code: :invalid_input, message: 'invalid image' } }, 400) - end - if !params[:datacenter].nil? && - $DATACENTERS['datacenters'].none? do |x| - datacenter = x if [x['id'].to_s, x['name']].include?(params[:datacenter].to_s) - end - error!({ error: { code: :invalid_input, message: 'invalid datacenter' } }, 400) - end - if !params[:location].nil? && - $LOCATIONS['locations'].none? do |x| - [x['id'].to_s, x['name']].include?(params[:location].to_s) - end - error!({ error: { code: :invalid_input, message: 'invalid location' } }, 400) - end - if params[:ssh_keys].to_a.any? do |id| - $SSH_KEYS['ssh_keys'].none? { |x| id.to_s == x['id'].to_s } - end - error!({ error: { code: :invalid_input, message: 'invalid ssh key' } }, 400) - end - id = $SERVER_ID += 1 - s = { - server: { - id: id, - name: params[:name], - server_type: server_type, - datacenter: datacenter, - image: image, - rescue_enabled: false, - locked: false, - backup_window: '22-02', - outgoing_traffic: 123, - ingoing_traffic: 123, - included_traffic: 123, - iso: nil, - status: 'initalizing', - created: Time.now.iso8601, - protection: { - delete: false, - rebuild: false - }, - public_net: { - ipv4: { - ip: '1.2.3.4', - blocked: false, - dns_ptr: 'example.com' - }, - ipv6: { - ip: 'fe80::1/64', - blocked: false, - dns_ptr: [] - }, - floating_ips: [], - volumes: [] - }, - labels: params[:labels] - }, - action: Action.add( - status: 'running', - command: 'create_server', - resources: [{ id: id, type: 'server' }] - ), - root_password: 'test123' - }.deep_stringify_keys - $SERVERS['servers'] << s['server'] - Thread.new do - sleep(0.5) - s['server']['status'] = params[:start_after_create] ? 'running' : 'off' - $ACTIONS['actions'].find { |x| x['id'] == s['action']['id'] }['status'] = 'success' - end - s - end - - params do - optional :name, type: String - end - get do - dc = $SERVERS.deep_dup - - dc['servers'].select! { |x| x['name'] == params[:name] } unless params[:name].nil? - unless params[:label_selector].nil? - dc['servers'].select! do |x| - FakeService.label_selector_matches(params[:label_selector], x['labels']) - end - end - - dc - end - end - end - end -end diff --git a/spec/fake_service/server_type.rb b/spec/fake_service/server_type.rb deleted file mode 100644 index cb49c40e..00000000 --- a/spec/fake_service/server_type.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $SERVER_TYPES = { - 'server_types' => [ - { - 'id' => 1, - 'name' => 'cx11', - 'description' => 'CX11', - 'cores' => 1, - 'memory' => 2.0, - 'disk' => 20, - 'prices' => [ - { - 'location' => 'fsn1', - 'price_hourly' => { - 'net' => '0.0040000000', - 'gross' => '0.0047600000000000' - }, - 'price_monthly' => { - 'net' => '2.4900000000', - 'gross' => '2.9631000000000000' - } - }, - { - 'location' => 'nbg1', - 'price_hourly' => { - 'net' => '0.0040000000', - 'gross' => '0.0047600000000000' - }, - 'price_monthly' => { - 'net' => '2.4900000000', - 'gross' => '2.9631000000000000' - } - } - ], - 'storage_type' => 'local' - } - ], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class ServerType < Grape::API - group :server_types do - params do - requires :id, type: Integer - end - route_param :id do - get do - x = $SERVER_TYPES['server_types'].find { |x| x['id'] == params[:id] } - error!({ error: { code: :not_found } }, 404) if x.nil? - { server_type: x } - end - end - - params do - optional :name, type: String - end - get do - if params.key?(:name) - dc = $SERVER_TYPES.deep_dup - dc['server_types'].select! { |x| x['name'] == params[:name] } - dc - else - $SERVER_TYPES - end - end - end - end - end -end diff --git a/spec/fake_service/ssh_key.rb b/spec/fake_service/ssh_key.rb deleted file mode 100644 index 5f0d0948..00000000 --- a/spec/fake_service/ssh_key.rb +++ /dev/null @@ -1,94 +0,0 @@ -# frozen_string_literal: true - -module Hcloud - module FakeService - $SSH_KEY_ID = 0 - $SSH_KEYS = { - 'ssh_keys' => [], - 'meta' => { - 'pagination' => { - 'page' => 1, - 'per_page' => 25, - 'previous_page' => nil, - 'next_page' => nil, - 'last_page' => 1, - 'total_entries' => 2 - } - } - } - - class SSHKey < Grape::API - group :ssh_keys do - params do - requires :id, type: Integer - end - route_param :id do - before_validation do - @x = $SSH_KEYS['ssh_keys'].find { |x| x['id'].to_s == params[:id].to_s } - error!({ error: { code: :not_found } }, 404) if @x.nil? - end - get do - { ssh_key: @x } - end - - params do - optional :name, type: String - end - put do - @x['name'] = params[:name] unless params[:name].nil? - @x['labels'] = params[:labels] unless params[:labels].nil? - { ssh_key: @x } - end - - delete do - $SSH_KEYS['ssh_keys'].delete(@x) - @body = nil - status 204 - end - end - - params do - optional :name, type: String - optional :public_key, type: String - end - post do - error!({ error: { code: :invalid_input } }, 400) if params[:name].nil? - unless params[:public_key].to_s.start_with?('ssh-') - error!({ error: { code: :invalid_input } }, 400) - end - if $SSH_KEYS['ssh_keys'].any? { |x| params[:public_key] == x['public_key'] } - error!({ error: { code: :uniqueness_error } }, 400) - end - if $SSH_KEYS['ssh_keys'].any? { |x| params[:name] == x['name'] } - error!({ error: { code: :uniqueness_error } }, 400) - end - key = { - 'id' => $SSH_KEY_ID += 1, - 'name' => params[:name], - 'fingerprint' => 0.upto(15).map { rand(0..255) }.map { |num| num.to_s(16) }.join(':'), - 'public_key' => params[:public_key], - 'labels' => params[:labels] - } - $SSH_KEYS['ssh_keys'] << key - { ssh_key: key } - end - - params do - optional :name, type: String - end - get do - ssh_keys = $SSH_KEYS.deep_dup - - ssh_keys['ssh_keys'].select! { |x| x['name'] == params[:name] } unless params[:name].nil? - unless params[:label_selector].nil? - ssh_keys['ssh_keys'].select! do |x| - FakeService.label_selector_matches(params[:label_selector], x['labels']) - end - end - - ssh_keys - end - end - end - end -end diff --git a/spec/hcloud/base_spec.rb b/spec/hcloud/base_spec.rb deleted file mode 100644 index f7233ff9..00000000 --- a/spec/hcloud/base_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe 'Generic' do - let :client do - Hcloud::Client.new(token: 'secure') - end - - let :uclient do - Hcloud::Client.new(token: 'invalid') - end - - it 'preload all constants' do - Hcloud.constants.each do |klass| - Hcloud.send(:const_get, klass) - end - end - - it 'check authorized' do - expect(client.authorized?).to be(true) - end - - it 'check unauthorized' do - expect(uclient.authorized?).to be(false) - end -end diff --git a/spec/integration/datacenter_spec.rb b/spec/integration/datacenter_spec.rb index 8b1c2fb0..a6b56058 100644 --- a/spec/integration/datacenter_spec.rb +++ b/spec/integration/datacenter_spec.rb @@ -2,36 +2,45 @@ require 'spec_helper' -describe 'Datacenter' do - let :client do - Hcloud::Client.new(token: 'secure') - end +describe 'Datacenter', :integration do + let(:valid_name) { 'fsn1-dc14' } + it 'fetchs datacenters' do - expect(client.datacenters.count).to eq(2) + # datacenters are Hetzner-managed, there must always be at least one + expect(client.datacenters.count).to be_a(Integer).and be > 0 + expect(client.datacenters).to all(be_a(Hcloud::Datacenter)) end it '#[] -> find by id' do - expect(client.datacenters[1].id).to eq(1) + expect(client.datacenters[client.datacenters.first.id]).to be_a Hcloud::Datacenter end it '#[] -> find by id, handle nonexistent' do - expect(client.datacenters[3]).to be nil + expect(client.datacenters[0]).to be nil end it '#find -> find by id' do - expect(client.datacenters.find(1).id).to eq(1) + expect(client.datacenters.find(client.datacenters.first.id)).to be_a Hcloud::Datacenter end it '#find -> find by id, handle nonexistent' do - expect { client.datacenters.find(3).id }.to raise_error(Hcloud::Error::NotFound) + expect { client.datacenters.find(0) }.to raise_error(Hcloud::Error::NotFound) end it '#[] -> filter by name' do - expect(client.datacenters['fsn1-dc8'].name).to eq('fsn1-dc8') + expect(client.datacenters[valid_name].name).to eq(valid_name) end it '#[] -> filter by name, handle nonexistent' do - expect(client.datacenters['fsn1-dc3']).to be nil + expect(client.datacenters['fsn42-dc42']).to be nil + end + + it '#find_by -> filter by name' do + expect(client.datacenters.find_by(name: valid_name).name).to eq(valid_name) + end + + it '#find_by -> filter by name, handle nonexistent' do + expect(client.datacenters.find_by(name: 'fsn42-dc42')).to be nil end it '#[] -> filter by name, handle invalid format' do diff --git a/spec/integration/firewall_spec.rb b/spec/integration/firewall_spec.rb index 31d606f7..610185b6 100644 --- a/spec/integration/firewall_spec.rb +++ b/spec/integration/firewall_spec.rb @@ -2,14 +2,8 @@ require 'spec_helper' -describe 'Firewall' do - let :client do - Hcloud::Client.new(token: 'secure') - end - - it 'fetch firewalls' do - expect(client.firewalls.count).to eq(0) - end +describe 'Firewall', :integration, integration_helper: :server do + let(:firewall_name) { resource_name('firewall') } it 'create new firewall, handle missing name' do expect { client.firewalls.create(name: nil) }.to( @@ -18,15 +12,20 @@ end it 'create new firewall, only name' do - actions, firewall = client.firewalls.create(name: 'fw') - expect(actions).to all be_a Hcloud::Action + actions, firewall = client.firewalls.create(name: firewall_name) + expect(actions).to all(be_a(Hcloud::Action)) expect(firewall).to be_a Hcloud::Firewall - expect(firewall.id).to be_a Integer - expect(firewall.name).to eq('fw') + expect(firewall.name).to eq(firewall_name) + end + + it 'fetch firewalls' do + # there must be at least one after we have created one + expect(client.firewalls.count).to be_an(Integer).and be > 0 + expect(client.firewalls).to all(be_a(Hcloud::Firewall)) end it 'create new firewall, uniq name' do - expect { client.firewalls.create(name: 'fw') }.to( + expect { client.firewalls.create(name: firewall_name) }.to( raise_error(Hcloud::Error::UniquenessError) ) end @@ -35,47 +34,57 @@ rules = [{ 'protocol' => 'tcp', 'source_ips' => ['192.0.2.0/24'], - 'port' => 80, + 'port' => '80', 'direction' => 'in', 'description' => 'HTTP access' }] + server_id = client.servers[helper_name].id apply_to = [{ 'server' => { - 'id' => 1 + 'id' => server_id }, 'type' => 'server' }] actions, firewall = client.firewalls.create( - name: 'fw-rules', + name: resource_name('firewall2'), rules: rules, apply_to: apply_to, labels: { 'source' => 'unittest' } ) - expect(actions).to all be_a Hcloud::Action + + expect(actions).to all(be_a(Hcloud::Action)) + actions.each do |action| + wait_for_action(firewall, action.id) + end + + firewall = client.firewalls[resource_name('firewall2')] expect(firewall).to be_a Hcloud::Firewall expect(firewall.id).to be_a Integer - expect(firewall.name).to eq('fw-rules') + expect(firewall.name).to eq(resource_name('firewall2')) expect(firewall.labels).to eq({ 'source' => 'unittest' }) expect(firewall.rules.length).to eq(1) expect(firewall.rules[0]['protocol']).to eq('tcp') expect(firewall.rules[0]['source_ips']).to eq(['192.0.2.0/24']) - expect(firewall.rules[0]['port']).to eq(80) + expect(firewall.rules[0]['port']).to eq('80') expect(firewall.rules[0]['direction']).to eq('in') expect(firewall.rules[0]['description']).to eq('HTTP access') expect(firewall.applied_to.length).to eq(1) - expect(firewall.applied_to[0]['server']['id']).to eq(1) + expect(firewall.applied_to[0]['server']['id']).to eq(server_id) expect(firewall.applied_to[0]['type']).to eq('server') - end - it 'fetch firewalls' do - expect(client.firewalls.count).to eq(2) + # remove firewall from server or we cannot delete the firewall later + remove_from = [{ + 'server' => { + 'id' => server_id + }, + 'type' => 'server' + }] + actions = firewall.remove_from_resources(remove_from: remove_from) + wait_for_action(firewall, actions[0].id) end it '#[] -> find by id' do - expect(client.firewalls.first).to be_a Hcloud::Firewall - id = client.firewalls.first.id - expect(id).to be_a Integer - expect(client.firewalls[id]).to be_a Hcloud::Firewall + expect(client.firewalls[client.firewalls.first.id]).to be_a Hcloud::Firewall end it '#[] -> find by id, handle nonexistent' do @@ -83,10 +92,7 @@ end it '#find -> find by id' do - expect(client.firewalls.first).to be_a Hcloud::Firewall - id = client.firewalls.first.id - expect(id).to be_a Integer - expect(client.firewalls.find(id)).to be_a Hcloud::Firewall + expect(client.firewalls.find(client.firewalls.first.id)).to be_a Hcloud::Firewall end it '#find -> find by id, handle nonexistent' do @@ -94,102 +100,108 @@ end it '#[] -> filter by name' do - expect(client.firewalls['fw']).to be_a Hcloud::Firewall - expect(client.firewalls['fw'].name).to eq('fw') - expect(client.firewalls['fw'].rules.length).to eq(0) + expect(client.firewalls[firewall_name]).to be_a Hcloud::Firewall + expect(client.firewalls[firewall_name].name).to eq(firewall_name) end it '#[] -> filter by name, handle nonexistent' do - expect(client.firewalls['fw-missing']).to be nil + expect(client.firewalls[resource_name(nonexistent_name)]).to be nil end it '#set_rules' do rules = [{ 'protocol' => 'tcp', 'source_ips' => ['192.0.2.0/30'], - 'port' => 21, + 'port' => '21', 'direction' => 'in', 'description' => 'FTP access' }] + actions = client.firewalls[firewall_name].set_rules(rules: rules) + expect(actions.count).to be_a(Integer).and be > 0 + expect(actions).to all(be_a(Hcloud::Action)) - expect do - actions = client.firewalls['fw'].set_rules(rules: rules) - expect(actions[0].command).to eq('set_rules') - end.to change { client.actions.count }.by(1) - - expect(client.firewalls['fw'].rules.length).to eq(1) - expect(client.firewalls['fw'].rules[0][:protocol]).to eq('tcp') - expect(client.firewalls['fw'].rules[0][:source_ips]).to eq(['192.0.2.0/30']) - expect(client.firewalls['fw'].rules[0][:port]).to eq(21) - expect(client.firewalls['fw'].rules[0][:direction]).to eq('in') - expect(client.firewalls['fw'].rules[0][:description]).to eq('FTP access') - - expect(client.firewalls['fw'].actions.count).to eq(1) + firewall = client.firewalls[firewall_name] + expect(firewall.rules.length).to eq(1) + expect(firewall.rules[0][:protocol]).to eq('tcp') + expect(firewall.rules[0][:source_ips]).to eq(['192.0.2.0/30']) + expect(firewall.rules[0][:port]).to eq('21') + expect(firewall.rules[0][:direction]).to eq('in') + expect(firewall.rules[0][:description]).to eq('FTP access') end it '#apply_to_resources' do + server_id = client.servers[helper_name].id apply_to = [{ 'server' => { - 'id' => 42 - }, - 'type' => 'server' - }, { - 'server' => { - 'id' => 1 + 'id' => server_id }, 'type' => 'server' }] + actions = nil expect do - actions = client.firewalls['fw'].apply_to_resources(apply_to: apply_to) - expect(actions[0].command).to eq('apply_to_resources') - end.to change { client.actions.count }.by(1) + actions = client.firewalls[firewall_name].apply_to_resources(apply_to: apply_to) + end.to change { client.firewalls[firewall_name].actions.count }.by(1) - expect(client.firewalls['fw'].applied_to.length).to eq(2) - expect(client.firewalls['fw'].applied_to.map { |res| res[:server][:id] }).to eq([42, 1]) - expect(client.firewalls['fw'].actions.count).to eq(2) + expect(actions).to all(be_a(Hcloud::Action)) + expect(actions[0].command).to eq('apply_firewall') + + firewall = client.firewalls[firewall_name] + expect(firewall.applied_to.length).to eq(1) + expect(firewall.applied_to[0][:server][:id]).to eq(server_id) + + wait_for_action(firewall, actions[0].id) end it '#apply_to_resources, missing apply_to' do - expect { client.firewalls['fw'].apply_to_resources(apply_to: nil) }.to( + expect { client.firewalls[firewall_name].apply_to_resources(apply_to: nil) }.to( raise_error(Hcloud::Error::InvalidInput) ) end it '#remove_from_resources' do + firewall = client.firewalls[firewall_name] + server_id = client.servers[helper_name].id + remove_from = [{ 'server' => { - 'id' => 1 + 'id' => server_id }, 'type' => 'server' }] + actions = nil expect do - actions = client.firewalls['fw'].remove_from_resources(remove_from: remove_from) - expect(actions[0].command).to eq('remove_from_resources') - end.to change { client.actions.count }.by(1) + actions = firewall.remove_from_resources(remove_from: remove_from) + end.to change { firewall.actions.count }.by(1) - expect(client.firewalls['fw'].applied_to.length).to eq(1) - expect(client.firewalls['fw'].applied_to[0][:server][:id]).to eq(42) - expect(client.firewalls['fw'].actions.count).to eq(3) + expect(actions).to all(be_a(Hcloud::Action)) + expect(actions[0].command).to eq('remove_firewall') + + firewall = client.firewalls[firewall_name] + expect(firewall.applied_to.length).to eq(0) + + wait_for_action(firewall, actions[0].id) end it '#remove_from_resources, missing remove_from' do - expect { client.firewalls['fw'].remove_from_resources(remove_from: nil) }.to( + expect { client.firewalls[firewall_name].remove_from_resources(remove_from: nil) }.to( raise_error(Hcloud::Error::InvalidInput) ) end it '#update(name:)' do - id = client.firewalls['fw'].id - expect(id).to be_a Integer - expect(client.firewalls.find(id).name).to eq('fw') - expect(client.firewalls.find(id).update(name: 'firewall').name).to eq('firewall') - expect(client.firewalls.find(id).name).to eq('firewall') + id = client.firewalls[firewall_name].id + new_name = resource_name('firewall-new') + + expect(client.firewalls.find(id).name).to eq(firewall_name) + expect(client.firewalls.find(id).update(name: new_name).name).to eq(new_name) + expect(client.firewalls.find(id).name).to eq(new_name) end it '#update(labels:)' do - id = client.firewalls['firewall'].id + id = client.firewalls[resource_name('firewall-new')].id + firewall = client.firewalls[id] updated = firewall.update(labels: { 'source' => 'update' }) expect(updated.labels).to eq({ 'source' => 'update' }) @@ -203,10 +215,11 @@ end it '#destroy' do - expect(client.firewalls.first).to be_a Hcloud::Firewall - id = client.firewalls.first.id - expect(id).to be_a Integer - expect(client.firewalls.find(id).destroy).to be_a Hcloud::Firewall - expect(client.firewalls[id]).to be nil + [resource_name('firewall-new'), resource_name('firewall2')].each do |name| + to_delete = client.firewalls[name] + expect(to_delete).to be_a Hcloud::Firewall + expect(to_delete.destroy).to be_a Hcloud::Firewall + expect(client.firewalls[to_delete.id]).to be nil + end end end diff --git a/spec/integration/floating_ip_spec.rb b/spec/integration/floating_ip_spec.rb new file mode 100644 index 00000000..8f52dc3e --- /dev/null +++ b/spec/integration/floating_ip_spec.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'FloatingIP', :integration, integration_helper: :server do + let(:fip_name) { resource_name('ip') } + + before(:context) do + # collect all the floating IPs we create, because we cannot define a name + # on which we can query for floating IPs + @ids = [] + end + + it 'fetch floating ips' do + expect(client.floating_ips.count).to be_a Integer + end + + it '#create -> invalid type' do + expect do + client.floating_ips.create(type: 'moo', home_location: 'nbg1') + end.to raise_error(Hcloud::Error::InvalidInput) + expect do + client.floating_ips.create(type: 'moo', home_location: 'nbg1', server: 1) + end.to raise_error(Hcloud::Error::InvalidInput) + end + + it '#create -> invalid home_location' do + expect do + client.floating_ips.create(type: 'ipv4', home_location: 'nbg2') + end.to raise_error(Hcloud::Error::NotFound) + expect do + client.floating_ips.create(type: 'ipv4', home_location: 'nbg2', server: 1) + end.to raise_error(Hcloud::Error::NotFound) + end + + it '#create -> invalid server' do + expect do + client.floating_ips.create(type: 'ipv4', home_location: 'nbg1', server: 'hui') + end.to raise_error(Hcloud::Error::InvalidInput) + end + + it '#create(type: ipv4, server:)' do + server_id = client.servers[helper_name].id + + a, f = nil + expect do + a, f = client.floating_ips.create( + type: 'ipv4', server: server_id, labels: { 'source' => 'create' } + ) + end.not_to raise_error + + expect(a).to be_a Hcloud::Action + expect(a.status).to eq('running') + expect(a.command).to eq('assign_floating_ip') + + expect(f).to be_a Hcloud::FloatingIP + expect(f.ip).to be_a String + expect(f.blocked).to be false + expect(f.home_location.name).to be_a String + expect(f.labels).to eq({ 'source' => 'create' }) + + @ids << f.id + end + + it "#create(type: ipv4, server:, home_location: 'fsn1')" do + server_id = client.servers[helper_name].id + + a, f = nil + expect do + a, f = client.floating_ips.create(type: 'ipv4', home_location: 'fsn1', server: server_id) + end.not_to raise_error + + expect(a).to be_a Hcloud::Action + expect(a.status).to eq('running') + expect(a.command).to eq('assign_floating_ip') + + expect(f).to be_a Hcloud::FloatingIP + expect(f.ip).to be_a String + expect(f.blocked).to be false + expect(f.home_location.name).to be_a String + + @ids << f.id + end + + it '#create(type: ipv4, home_location: nbg1)' do + a, f = nil + expect do + a, f = client.floating_ips.create(type: 'ipv4', home_location: 'nbg1') + end.not_to raise_error + + expect(a).to be nil + + expect(f).to be_a Hcloud::FloatingIP + expect(f.ip).to be_a String + expect(f.blocked).to be false + expect(f.home_location.name).to eq('nbg1') + + @ids << f.id + end + + it '#[] -> find by id' do + id = client.floating_ips.first.id + fip = client.floating_ips[id] + expect(fip).to be_a Hcloud::FloatingIP + expect(fip.id).to eq(id) + end + + it '#update(description:)' do + expect(client.floating_ips.find(@ids[0]).description).to be nil + expect { client.floating_ips.find(@ids[0]).update(description: fip_name) }.not_to raise_error + expect(client.floating_ips.find(@ids[0]).description).to eq(fip_name) + end + + it '#update(labels:)' do + ip = client.floating_ips.find(@ids[0]) + updated = ip.update(labels: { 'source' => 'update' }) + expect(updated.labels).to eq({ 'source' => 'update' }) + expect(client.floating_ips.find(@ids[0]).labels).to eq({ 'source' => 'update' }) + end + + it '#where -> find by label_selector' do + ips = client.floating_ips.where(label_selector: 'source=update').to_a + expect(ips.length).to eq(1) + expect(ips.first.labels).to include('source' => 'update') + end + + it '#unassign()' do + server_id = client.servers[helper_name].id + + expect(client.floating_ips.find(@ids[0]).server).to eq(server_id) + expect(client.floating_ips.find(@ids[0]).unassign).to be_a Hcloud::Action + expect(client.floating_ips.find(@ids[0]).server).to be nil + end + + it '#assign(server:)' do + server_id = client.servers[helper_name].id + + expect(client.floating_ips.find(@ids[0]).server).to be nil + expect(client.floating_ips.find(@ids[0]).assign(server: server_id)).to be_a Hcloud::Action + expect(client.floating_ips.find(@ids[0]).server).to eq(server_id) + end + + it '#change_dns_ptr' do + fip = client.floating_ips[@ids[0]] + expect(fip).to be_a Hcloud::FloatingIP + + expect(fip.change_dns_ptr(ip: fip.ip, dns_ptr: 'moo.example.com')).to be_a Hcloud::Action + + # update floating IP data + fip = client.floating_ips[@ids[0]] + expect(fip.dns_ptr.count).to eq(1) + expect(fip.dns_ptr[0]['ip']).to eq(fip.ip) + expect(fip.dns_ptr[0]['dns_ptr']).to eq('moo.example.com') + end + + it '#change_protection' do + expect(client.floating_ips[@ids[0]]).to be_a Hcloud::FloatingIP + expect(client.floating_ips[@ids[0]].protection).to be_a Hash + expect(client.floating_ips[@ids[0]].protection['delete']).to be false + + expect(client.floating_ips[@ids[0]].change_protection(delete: true)).to be_a Hcloud::Action + + expect(client.floating_ips[@ids[0]].protection).to be_a Hash + expect(client.floating_ips[@ids[0]].protection['delete']).to be true + + # reset to allow delete later + client.floating_ips[@ids[0]].change_protection(delete: false) + end + + it '#destroy()' do + @ids.each do |id| + to_delete = client.floating_ips[id] + expect(to_delete).to be_a Hcloud::FloatingIP + + expect { to_delete.destroy }.not_to raise_error + end + end +end diff --git a/spec/integration/image_spec.rb b/spec/integration/image_spec.rb new file mode 100644 index 00000000..546e322b --- /dev/null +++ b/spec/integration/image_spec.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Image', :integration, integration_helper: :server do + let(:image_name) { resource_name('image') } + + before(:context) do + # let's collect all the images we create, because we cannot easily access them by + # their description + @images = [] + end + + before(:each) do + # use the first of our newly created images when we need for perform an action + # on an image + @image_id = @images[0] + end + + it 'fetch images' do + expect(client.images.count).to be_a Integer + end + + it '#[] -> find by id (system image)' do + expect(client.images.first).to be_a Hcloud::Image + id = client.images.first.id + expect(id).to be_a Integer + expect(client.images[id]).to be_a Hcloud::Image + expect(client.images[id].name).to be_a String + expect(client.images[id].type).to be_a String + expect(client.images[id].status).to be_a String + expect(client.images[id].name).to be_a String + expect(client.images[id].description).to be_a String + expect(client.images[id].created_from).to be nil + expect(client.images[id].created).to be_a Time + expect(client.images[id].bound_to).to be nil + expect(client.images[id].os_flavor).to be_a String + expect(client.images[id].os_version).to be_a String + expect(client.images[id].rapid_deploy).to be true + end + + it 'create image for tests' do + server = client.servers[helper_name] + action, image = server.create_image( + type: 'snapshot', + description: image_name + ) + wait_for_action(server, action.id) + + @images << image.id + end + + it '#[] -> find by id (snapshot image)' do + expect(client.images[@image_id]).to be_a Hcloud::Image + expect(client.images[@image_id].name).to be nil + expect(client.images[@image_id].type).to eq('snapshot') + expect(client.images[@image_id].description).to eq(image_name) + expect(client.images[@image_id].created_from).to be_a Hash + expect(client.images[@image_id].created).to be_a Time + expect(client.images[@image_id].rapid_deploy).to be false + end + + it '#update(description:) - handle nil' do + expect { client.images[@image_id].update(description: nil) }.to( + raise_error(Hcloud::Error::InvalidInput) + ) + end + + it '#update(description:)' do + new_name = resource_name('image-new') + expect(client.images[@image_id].description).not_to eq(new_name) + expect(client.images[@image_id].update(description: new_name).description).to eq(new_name) + expect(client.images[@image_id].description).to eq(new_name) + end + + it '#update(type:) - handle invalid' do + expect { client.images[@image_id].update(type: 'moo') }.to( + raise_error(Hcloud::Error::InvalidInput) + ) + end + + it '#update(type:) - handle nil' do + expect { client.images[@image_id].update(type: nil) }.to( + raise_error(Hcloud::Error::InvalidInput) + ) + end + + it '#update(type:) - handle backup convert for predefinded images' do + expect { client.images[@image_id].update(type: 'backup') }.to( + raise_error(Hcloud::Error::InvalidInput) + ) + end + + it '#update(labels:)' do + image = client.images[@image_id] + updated = image.update(labels: { 'source' => 'update' }) + expect(updated.labels).to eq({ 'source' => 'update' }) + expect(client.images[@image_id].labels).to eq({ 'source' => 'update' }) + end + + it '#where -> find by label_selector' do + images = client.images.where(label_selector: 'source=update').to_a + expect(images.length).to eq(1) + expect(images.first.labels).to include('source' => 'update') + end + + it '#to_snapshot' do + # need a backup image, cannot convert snapshot to snapshot + server = client.servers[helper_name] + action = server.enable_backup + wait_for_action(server, action.id) + + action, image = server.create_image( + type: 'backup', + description: resource_name('image-backup') + ) + wait_for_action(server, action.id) + @images << image.id + + client.images[image.id].to_snapshot + expect(client.images[image.id].type).to eq('snapshot') + end + + it '#where(name:)' do + images = client.images.where(name: client.images.first.name) + expect(images.count).to eq(1) + + x = images.first + expect(x).to be_a Hcloud::Image + expect(x.name).to be_a String + expect(x.type).to be_a String + expect(x.status).to be_a String + expect(x.name).to be_a String + expect(x.description).to be_a String + expect(x.created_from).to be nil + expect(x.created).to be_a Time + expect(x.bound_to).to be nil + expect(x.os_flavor).to be_a String + expect(x.os_version).to be_a String + expect(x.rapid_deploy).to be true + end + + it '#where(name:) -> handle nonexistent' do + expect(client.images.where(name: nonexistent_name).count).to eq(0) + end + + it '#[] -> find by id, handle nonexistent' do + expect(client.images[0]).to be nil + end + + it '#find -> find by id' do + expect(client.images.first).to be_a Hcloud::Image + id = client.images.first.id + expect(id).to be_a Integer + expect(client.images.find(id)).to be_a Hcloud::Image + expect(client.images.find(id).name).to be_a String + expect(client.images.find(id).type).to be_a String + expect(client.images.find(id).status).to be_a String + expect(client.images.find(id).name).to be_a String + expect(client.images.find(id).description).to be_a String + expect(client.images.find(id).created_from).to be nil + expect(client.images.find(id).created).to be_a Time + expect(client.images.find(id).bound_to).to be nil + expect(client.images.find(id).os_flavor).to be_a String + expect(client.images.find(id).os_version).to be_a String + expect(client.images.find(id).rapid_deploy).to be true + end + + it '#find -> find by id, handle nonexistent' do + expect { client.images.find(0).id }.to raise_error(Hcloud::Error::NotFound) + end + + it '#[] -> filter by name' do + name = client.images.first.name + expect(client.images[name]).to be_a Hcloud::Image + expect(client.images[name].name).to be_a String + expect(client.images[name].type).to be_a String + expect(client.images[name].status).to be_a String + expect(client.images[name].name).to be_a String + expect(client.images[name].description).to be_a String + expect(client.images[name].created_from).to be nil + expect(client.images[name].created).to be_a Time + expect(client.images[name].bound_to).to be nil + expect(client.images[name].os_flavor).to be_a String + expect(client.images[name].os_version).to be_a String + expect(client.images[name].rapid_deploy).to be true + end + + it '#[] -> filter by name, handle nonexistent' do + expect(client.images['mooo']).to be nil + end + + it '#change_protection' do + expect(client.images[@image_id]).to be_a Hcloud::Image + expect(client.images[@image_id].protection).to be_a Hash + expect(client.images[@image_id].protection['delete']).to be false + + expect(client.images[@image_id].change_protection).to be_a Hcloud::Action + + expect(client.images[@image_id].protection).to be_a Hash + expect(client.images[@image_id].protection['delete']).to be false + + expect(client.images[@image_id].change_protection(delete: true)).to be_a Hcloud::Action + + expect(client.images[@image_id].protection).to be_a Hash + expect(client.images[@image_id].protection['delete']).to be true + + # disable protection again to allow delete + client.images[@image_id].change_protection(delete: false) + end + + it '#destroy()' do + @images.each do |image_id| + expect { client.images[image_id].destroy }.not_to raise_error + expect(client.images[image_id].deleted).not_to be(nil) + end + end +end diff --git a/spec/integration/iso_spec.rb b/spec/integration/iso_spec.rb index e768bd60..e14d4785 100644 --- a/spec/integration/iso_spec.rb +++ b/spec/integration/iso_spec.rb @@ -2,29 +2,23 @@ require 'spec_helper' -describe 'ISO' do - let :client do - Hcloud::Client.new(token: 'secure') - end - +describe 'ISO', :integration do it 'fetchs isos' do - expect(client.isos.count).to eq(1) + expect(client.isos.count).to be_an(Integer).and be > 0 end it '#[] -> find by id' do - expect(client.isos.first).to be_a Hcloud::Iso id = client.isos.first.id - expect(id).to be_a Integer + expect(id).to be_an Integer expect(client.isos[id]).to be_a Hcloud::Iso expect(client.isos[id].id).to eq(id) end it '#[] -> find by id, handle nonexistent' do - expect(client.isos[3]).to be nil + expect(client.isos[-1]).to be nil end it '#find -> find by id' do - expect(client.isos.first).to be_a Hcloud::Iso id = client.isos.first.id expect(id).to be_a Integer expect(client.isos.find(id)).to be_a Hcloud::Iso @@ -32,11 +26,10 @@ end it '#find -> find by id, handle nonexistent' do - expect { client.isos.find(3).id }.to raise_error(Hcloud::Error::NotFound) + expect { client.isos.find(-1).id }.to raise_error(Hcloud::Error::NotFound) end it '#[] -> filter by name' do - expect(client.isos.first).to be_a Hcloud::Iso name = client.isos.first.name expect(name).to be_a String expect(client.isos[name]).to be_a Hcloud::Iso @@ -44,11 +37,10 @@ end it '#[] -> filter by name, handle nonexistent' do - expect(client.isos['mooo']).to be nil + expect(client.isos[nonexistent_name]).to be nil end it '#find_by -> filter by name' do - expect(client.isos.first).to be_a Hcloud::Iso name = client.isos.first.name expect(name).to be_a String expect(client.isos.find_by(name: name)).to be_a Hcloud::Iso @@ -56,6 +48,6 @@ end it '#find_by -> filter by name, handle nonexistent' do - expect(client.isos.find_by(name: 'moo')).to be nil + expect(client.isos.find_by(name: nonexistent_name)).to be nil end end diff --git a/spec/integration/location_spec.rb b/spec/integration/location_spec.rb index 2a1fcabb..12301509 100644 --- a/spec/integration/location_spec.rb +++ b/spec/integration/location_spec.rb @@ -2,40 +2,32 @@ require 'spec_helper' -describe 'Location' do - before(:each) do - Hcloud::Client.connection = Hcloud::Client.new(token: 'secure') - end - after(:each) do - Hcloud::Client.connection = nil - end - let :client do - end +describe 'Location', :integration do it 'fetchs locations' do - expect(Hcloud::Location.count).to eq(2) + expect(client.locations.count).to be_an(Integer).and be > 0 end it '#[] -> find by id' do - expect(Hcloud::Location[1].id).to eq(1) + expect(client.locations[client.locations.first.id]).to be_a Hcloud::Location end it '#[] -> find by id, handle nonexistent' do - expect(Hcloud::Location[3]).to be nil + expect(client.locations[0]).to be nil end it '#find -> find by id' do - expect(Hcloud::Location.find(1).id).to eq(1) + expect(client.locations.find(client.locations.first.id)).to be_a Hcloud::Location end it '#find -> find by id, handle nonexistent' do - expect { Hcloud::Location.find(3).id }.to raise_error(Hcloud::Error::NotFound) + expect { client.locations.find(0).id }.to raise_error(Hcloud::Error::NotFound) end it '#[] -> filter by name' do - expect(Hcloud::Location['fsn1'].name).to eq('fsn1') + expect(client.locations['fsn1'].name).to eq('fsn1') end it '#[] -> filter by name, handle nonexistent' do - expect(Hcloud::Location['mooo']).to be nil + expect(client.locations[nonexistent_name]).to be nil end end diff --git a/spec/integration/network_spec.rb b/spec/integration/network_spec.rb index 94a5724f..24df61b5 100644 --- a/spec/integration/network_spec.rb +++ b/spec/integration/network_spec.rb @@ -2,13 +2,11 @@ require 'spec_helper' -describe 'Network' do - let :client do - Hcloud::Client.new(token: 'secure') - end +describe 'Network', :integration do + let(:network_name) { resource_name('network') } it 'fetch networks' do - expect(client.networks.count).to eq(0) + expect(client.networks.count).to be_a Integer end it 'create new network, handle missing name' do @@ -18,14 +16,14 @@ end it 'create new network, handle missing ip_range' do - expect { client.networks.create(name: 'testnet', ip_range: nil) }.to( + expect { client.networks.create(name: network_name, ip_range: nil) }.to( raise_error(Hcloud::Error::InvalidInput) ) end it 'create new network' do network = client.networks.create( - name: 'testnet', + name: network_name, ip_range: '192.168.0.0/16', routes: [{ destination: '10.0.0.0/24', @@ -40,7 +38,7 @@ ) expect(network).to be_a Hcloud::Network expect(network.id).to be_a Integer - expect(network.name).to eq('testnet') + expect(network.name).to eq(network_name) expect(network.routes[0][:destination]).to eq('10.0.0.0/24') expect(network.routes[0][:gateway]).to eq('192.168.0.10') expect(network.subnets[0][:ip_range]).to eq('192.168.0.0/24') @@ -50,13 +48,13 @@ end it 'create new network, uniq name' do - expect { client.networks.create(name: 'testnet', ip_range: '10.0.0.0/24') }.to( + expect { client.networks.create(name: network_name, ip_range: '10.0.0.0/24') }.to( raise_error(Hcloud::Error::UniquenessError) ) end it 'fetch networks' do - expect(client.networks.count).to eq(1) + expect(client.networks.count).to be_an(Integer).and be > 0 end it '#[] -> find by id' do @@ -64,7 +62,6 @@ id = client.networks.first.id expect(id).to be_a Integer expect(client.networks[id]).to be_a Hcloud::Network - expect(client.networks[id].name).to eq('testnet') end it '#[] -> find by id, handle nonexistent' do @@ -83,18 +80,18 @@ end it '#[] -> filter by name' do - expect(client.networks['testnet']).to be_a Hcloud::Network - expect(client.networks['testnet'].name).to eq('testnet') - expect(client.networks['testnet'].routes.length).to eq(1) - expect(client.networks['testnet'].subnets.length).to eq(1) + expect(client.networks[network_name]).to be_a Hcloud::Network + expect(client.networks[network_name].name).to eq(network_name) + expect(client.networks[network_name].routes.length).to eq(1) + expect(client.networks[network_name].subnets.length).to eq(1) end it '#[] -> filter by name, handle nonexistent' do - expect(client.networks['network-missing']).to be nil + expect(client.networks[nonexistent_name]).to be nil end it '#add_subnet' do - network = client.networks['testnet'] + network = client.networks[network_name] expect(network).to be_a Hcloud::Network network.add_subnet( @@ -103,53 +100,45 @@ ip_range: '192.168.1.0/24' ) - expect(client.networks['testnet'].subnets.length).to eq(2) - - expect(client.actions.count).to eq(1) - expect(client.networks['testnet'].actions.count).to eq(1) + expect(client.networks[network_name].subnets.length).to eq(2) end it '#del_subnet' do - network = client.networks['testnet'] + network = client.networks[network_name] expect(network).to be_a Hcloud::Network network.del_subnet(ip_range: '192.168.1.0/24') - expect(client.networks['testnet'].subnets.length).to eq(1) - - expect(client.actions.count).to eq(2) - expect(client.networks['testnet'].actions.count).to eq(2) + expect(client.networks[network_name].subnets.length).to eq(1) end it '#add_route' do - network = client.networks['testnet'] + network = client.networks[network_name] expect(network).to be_a Hcloud::Network network.add_route(destination: '10.0.1.0/24', gateway: '192.168.0.10') - expect(client.networks['testnet'].routes.length).to eq(2) - - expect(client.actions.count).to eq(3) - expect(client.networks['testnet'].actions.count).to eq(3) + expect(client.networks[network_name].routes.length).to eq(2) end it '#del_route' do - network = client.networks['testnet'] + network = client.networks[network_name] expect(network).to be_a Hcloud::Network network.del_route(destination: '10.0.1.0/24', gateway: '192.168.0.10') - expect(client.networks['testnet'].routes.length).to eq(1) - - expect(client.actions.count).to eq(4) - expect(client.networks['testnet'].actions.count).to eq(4) + expect(client.networks[network_name].routes.length).to eq(1) end it '#update(name:)' do - id = client.networks['testnet'].id + new_name = resource_name('network-new') + id = client.networks[network_name].id expect(id).to be_a Integer - expect(client.networks.find(id).name).to eq('testnet') - expect(client.networks.find(id).update(name: 'testing').name).to eq('testing') - expect(client.networks.find(id).name).to eq('testing') + expect(client.networks.find(id).name).to eq(network_name) + expect(client.networks.find(id).update(name: new_name).name).to eq(new_name) + expect(client.networks.find(id).name).to eq(new_name) + + # rename back + client.networks.find(id).update(name: network_name) end it '#update(labels:)' do @@ -167,10 +156,9 @@ end it '#destroy' do - expect(client.networks.first).to be_a Hcloud::Network - id = client.networks.first.id - expect(id).to be_a Integer - expect(client.networks.find(id).destroy).to be_a Hcloud::Network - expect(client.networks[id]).to be nil + to_delete = client.networks[network_name] + expect(to_delete).to be_a Hcloud::Network + expect(to_delete.destroy).to be_a Hcloud::Network + expect(client.networks[to_delete.id]).to be nil end end diff --git a/spec/integration/server_spec.rb b/spec/integration/server_spec.rb index 9751689e..83e4f817 100644 --- a/spec/integration/server_spec.rb +++ b/spec/integration/server_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe 'Server' do +describe 'Server', :integration do after :all do # The test fake for server uses threading with delay to change action status # from 'running' to 'success'. To make sure that all threads have completed @@ -11,115 +11,122 @@ sleep(0.5) end - let :client do - Hcloud::Client.new(token: 'secure') + let :sample_image do + # use a well known image, because a randomly chosen image might not have guest agent + # (guest agent is required to reset password) + client.images['ubuntu-22.04'] end - let :aclient do - Hcloud::Client.send(:remove_const, :MAX_ENTRIES_PER_PAGE) - Hcloud::Client.const_set(:MAX_ENTRIES_PER_PAGE, 1) - Hcloud::Client.new(token: 'secure', auto_pagination: true) + let :sample_datacenter do + client.datacenters['fsn1-dc14'] end - let :bclient do - Hcloud::Client.send(:remove_const, :MAX_ENTRIES_PER_PAGE) - Hcloud::Client.const_set(:MAX_ENTRIES_PER_PAGE, 2) - Hcloud::Client.new(token: 'secure', auto_pagination: true) + let :server_name do + resource_name('server') end it 'fetch server' do - expect(client.servers.count).to eq(0) + expect(client.servers.count).to be_a Integer end it 'create new server, handle missing name' do - expect { client.servers.create(server_type: 'cx11', image: 1) }.to( + expect { client.servers.create(server_type: 'cx11', image: sample_image.id) }.to( raise_error(ArgumentError) ) end it 'create new server, handle invalid name' do - expect { client.servers.create(server_type: 'cx11', image: 1, name: 'moo_moo') }.to( - raise_error(Hcloud::Error::InvalidInput) - ) + expect do + client.servers.create(server_type: 'cx11', image: sample_image.id, name: 'moo_moo') + end.to raise_error(Hcloud::Error::InvalidInput) end it 'create new server, handle missing server_type' do - expect { client.servers.create(name: 'moo', image: 1) }.to( + expect { client.servers.create(name: server_name, image: sample_image.id) }.to( raise_error(ArgumentError) ) end it 'create new server, handle invalid server_type' do - expect { client.servers.create(server_type: 'cx111', image: 1, name: 'moo') }.to( - raise_error(Hcloud::Error::InvalidInput) - ) + expect do + client.servers.create(server_type: 'cx111', image: sample_image.id, name: server_name) + end.to(raise_error(Hcloud::Error::InvalidInput)) end it 'create new server, handle missing image' do - expect { client.servers.create(name: 'moo', server_type: 'cx11') }.to( + expect { client.servers.create(name: server_name, server_type: 'cx11') }.to( raise_error(ArgumentError) ) end it 'create new server, handle invalid image' do - expect { client.servers.create(server_type: 'cx11', image: 1234, name: 'moo') }.to( + expect { client.servers.create(server_type: 'cx11', image: 0, name: server_name) }.to( raise_error(Hcloud::Error::InvalidInput) ) end it 'create new server, handle invalid datacenter' do - expect { client.servers.create(name: 'moo', server_type: 'cx11', image: 1, datacenter: 5) }.to( - raise_error(Hcloud::Error::InvalidInput) - ) + expect do + client.servers.create( + name: server_name, + server_type: 'cx11', + image: sample_image.id, + datacenter: 0 + ) + end.to raise_error(Hcloud::Error::InvalidInput) end it 'create new server, handle invalid location' do - expect { client.servers.create(name: 'moo', server_type: 'cx11', image: 1, location: 5) }.to( - raise_error(Hcloud::Error::InvalidInput) - ) + expect do + client.servers.create( + name: server_name, + server_type: 'cx11', + image: sample_image.id, + location: 0 + ) + end.to raise_error(Hcloud::Error::InvalidInput) end it 'create new server' do action, server, pass = nil expect do action, server, pass = client.servers.create( - name: 'moo', server_type: 'cx11', image: 1, labels: { 'source' => 'test' } + name: server_name, + server_type: 'cx11', + image: sample_image.id, + labels: { 'source' => 'test' } ) end.not_to(raise_error) - expect(client.actions.per_page(1).page(1).count).to eq(1) - expect(aclient.actions.count).to eq(1) - expect(client.actions.per_page(1).page(2).count).to eq(0) + expect(action).to be_a Hcloud::Action expect(server.id).to be_a Integer - expect(server.name).to eq('moo') - expect(server.rescue_enabled).to be false - expect(server.backup_window).to eq('22-02') - expect(server.datacenter.id).to eq(1) + expect(server.name).to eq(server_name) + expect(server.datacenter.id).to be_a Integer expect(server.locked).to be false - expect(server.iso).to be nil expect(server.created).to be_a Time - expect(server.outgoing_traffic).to eq(123) - expect(server.ingoing_traffic).to eq(123) - expect(server.included_traffic).to eq(123) - expect(server.image.id).to eq(1) - expect(server.status).to eq('initalizing') - expect(server.image.id).to eq(1) + expect(server.image.id).to eq(sample_image.id) + expect(server.status).to eq('initializing') expect(server.labels).to eq({ 'source' => 'test' }) expect(action.status).to eq('running') expect(action.command).to eq('create_server') - expect(pass).to eq('test123') + expect(pass).to be_a String + + wait_for_action(client.servers[server_name], action.id) end it 'create new server, custom datacenter' do action, server, pass = nil expect do - action, server, pass = bclient.servers.create( - name: 'foo', server_type: 'cx11', image: 1, datacenter: 2 + action, server, pass = client.servers.create( + name: resource_name('server2'), + server_type: 'cx11', + image: sample_image.id, + datacenter: sample_datacenter.id ) end.not_to(raise_error) - expect(bclient.actions.count).to eq(2) - expect(server.actions.count).to eq(1) + expect(action).to be_a Hcloud::Action + expect(server.actions.count).to be_a Integer expect(server.id).to be_a Integer - expect(server.datacenter.id).to eq(2) + expect(server.datacenter.id).to eq(sample_datacenter.id) expect(action.status).to eq('running') end @@ -127,7 +134,10 @@ action, server, pass = nil expect do action, server, pass = client.servers.create( - name: 'bar', server_type: 'cx11', image: 1, start_after_create: true + name: resource_name('server3'), + server_type: 'cx11', + image: sample_image.id, + start_after_create: true ) end.not_to(raise_error) expect(server.id).to be_a Integer @@ -135,62 +145,16 @@ end it 'create new server, handle name uniqness' do - expect { client.servers.create(name: 'moo', server_type: 'cx11', image: 1) }.to( - raise_error(Hcloud::Error::UniquenessError) - ) - end - - it 'check succeded servers and actions' do - expect(client.servers.all? { |x| x.status == 'initalizing' }).to be true - expect(client.actions.where(status: 'running') - .select { |x| x.resources.first[:type] == 'server' }.size).to eq(3) - expect(client.actions.per_page(1).where(status: 'running').count).to eq(1) - expect(client.actions.per_page(2).where(status: 'running').count).to eq(2) - expect(client.actions.per_page(2).page(2).where(status: 'running').count).to eq(1) - expect(client.actions.per_page(2).page(2).count).to eq(1) - expect(client.actions.per_page(2).page(1).count).to eq(2) - expect(client.actions.order(:id).map(&:id)).to eq([1, 2, 3]) - expect(client.actions.order(id: :asc).map(&:id)).to eq([1, 2, 3]) - expect(client.actions.order(id: :desc).map(&:id)).to eq([3, 2, 1]) - expect { client.actions.order(1).map(&:id) }.to raise_error(ArgumentError) - expect(client.actions.per_page(1).page(1).all.pagination.total_entries).to eq(3) - expect(client.actions.per_page(1).page(1).all.pagination.last_page).to eq(3) - expect(client.actions.per_page(1).page(1).all.pagination.next_page).to eq(2) - expect(client.actions.per_page(1).page(1).all.pagination.previous_page).to be nil - expect(client.actions.per_page(1).page(2).all.pagination.next_page).to eq(3) - expect(client.actions.per_page(1).page(2).all.pagination.previous_page).to eq(1) - expect(client.actions.per_page(1).page(3).all.pagination.next_page).to be nil - expect(client.actions.per_page(1).page(3).all.pagination.previous_page).to eq(2) - expect(client.actions.per_page(10).page(3).count).to eq(0) - expect(aclient.actions.count).to eq(3) - expect(bclient.actions.limit(3).count).to eq(3) - expect(aclient.actions.limit(2).count).to eq(2) - expect(aclient.actions.limit(3).count).to eq(3) - expect(aclient.actions.limit(4).count).to eq(3) - expect(aclient.actions.all.pagination).to eq(:auto) - sleep(0.6) - expect(client.servers.none? { |x| x.status == 'initalizing' }).to be true - expect(client.servers.group_by(&:status).transform_values(&:size)).to( - eq('off' => 2, 'running' => 1) - ) - expect(client.actions.where(status: 'success') - .select { |x| x.resources.first[:type] == 'server' }.size).to eq(3) + expect do + client.servers.create(name: server_name, server_type: 'cx11', image: sample_image.id) + end.to(raise_error(Hcloud::Error::UniquenessError)) end it '#find()' do - server = client.servers.find(1) - expect(server.id).to be_a Integer - expect(server.name).to eq('moo') - expect(server.rescue_enabled).to be false - expect(server.backup_window).to eq('22-02') - expect(server.datacenter.id).to eq(1) - expect(server.locked).to be false - expect(server.iso).to be nil - expect(server.created).to be_a Time - expect(server.outgoing_traffic).to eq(123) - expect(server.ingoing_traffic).to eq(123) - expect(server.included_traffic).to eq(123) - expect(server.image.id).to eq(1) + id = client.servers.first.id + server = client.servers.find(id) + expect(server).to be_a Hcloud::Server + expect(server.id).to eq(id) end it '#find() -> handle error' do @@ -198,55 +162,23 @@ end it '#find_by(name:)' do - server = client.servers.find_by(name: 'moo') - expect(server.id).to be_a Integer - expect(server.name).to eq('moo') - expect(server.rescue_enabled).to be false - expect(server.backup_window).to eq('22-02') - expect(server.datacenter.id).to eq(1) - expect(server.locked).to be false - expect(server.iso).to be nil - expect(server.created).to be_a Time - expect(server.outgoing_traffic).to eq(123) - expect(server.ingoing_traffic).to eq(123) - expect(server.included_traffic).to eq(123) - expect(server.image.id).to eq(1) + server = client.servers.find_by(name: server_name) + expect(server.name).to eq(server_name) end it '#[string]' do - server = client.servers['moo'] - expect(server.id).to be_a Integer - expect(server.name).to eq('moo') - expect(server.rescue_enabled).to be false - expect(server.backup_window).to eq('22-02') - expect(server.datacenter.id).to eq(1) - expect(server.locked).to be false - expect(server.iso).to be nil - expect(server.created).to be_a Time - expect(server.outgoing_traffic).to eq(123) - expect(server.ingoing_traffic).to eq(123) - expect(server.included_traffic).to eq(123) - expect(server.image.id).to eq(1) + server = client.servers[server_name] + expect(server.name).to eq(server_name) end it '#[string] -> handle nil' do - expect(client.servers['']).to be nil + expect(client.servers[nonexistent_name]).to be nil end it '#[integer]' do - server = client.servers[1] - expect(server.id).to be_a Integer - expect(server.name).to eq('moo') - expect(server.rescue_enabled).to be false - expect(server.backup_window).to eq('22-02') - expect(server.datacenter.id).to eq(1) - expect(server.locked).to be false - expect(server.iso).to be nil - expect(server.created).to be_a Time - expect(server.outgoing_traffic).to eq(123) - expect(server.ingoing_traffic).to eq(123) - expect(server.included_traffic).to eq(123) - expect(server.image.id).to eq(1) + id = client.servers.first.id + server = client.servers[id] + expect(server.id).to eq(id) end it '#[integer] -> handle nil' do @@ -254,21 +186,25 @@ end it '#update(name:)' do - expect(client.servers.find(1).name).to eq('moo') + new_name = resource_name('server-new') + id = client.servers[server_name].id server = nil - expect { server = client.servers[1].update(name: 'foo') }.to( + expect { server = client.servers[id].update(name: resource_name('server2')) }.to( raise_error(Hcloud::Error::UniquenessError) ) - expect { server = client.servers[1].update(name: 'hui') }.not_to raise_error - expect(server.name).to eq('hui') - expect(client.servers.find(1).name).to eq('hui') + expect { server = client.servers[id].update(name: new_name) }.not_to raise_error + expect(server.name).to eq(new_name) + expect(client.servers.find(id).name).to eq(new_name) + + # rename back + server = client.servers[id].update(name: server_name) end it '#update(labels:)' do - server = client.servers.find(1) + server = client.servers[server_name] updated = server.update(labels: { 'source' => 'update' }) expect(updated.labels).to eq({ 'source' => 'update' }) - expect(client.servers.find(1).labels).to eq({ 'source' => 'update' }) + expect(client.servers[server_name].labels).to eq({ 'source' => 'update' }) end it '#where -> find by label_selector' do @@ -277,93 +213,102 @@ expect(servers.first.labels).to include('source' => 'update') end - it '#destroy' do - action = nil - expect { action = client.servers[1].destroy }.not_to raise_error - expect(action.status).to eq('success') - expect(client.servers[1]).to be nil - end - - it 'check server actions' do - expect(client.servers[2].actions.count).to eq(1) - expect(id = client.servers[2].actions.first.id).to be_a Integer - expect(client.servers[2].actions[id].id).to eq(id) - end - - it '#poweroff' do - expect(client.servers[2].poweroff).to be_a Hcloud::Action - expect(client.servers[2].actions.count { |x| x.command == 'stop_server' }).to eq(1) - end + it '#reset_password' do + # server might need some time until guest agent is installed + sleep 30 - it '#poweron' do - expect { client.servers[2].poweron }.to raise_error(Hcloud::Error::Locked) - sleep(0.5) - expect(client.servers[2].status).to eq('off') - expect(client.servers[2].poweron).to be_a Hcloud::Action - expect(client.servers[2].actions.count { |x| x.command == 'start_server' }).to eq(1) - end + action, pass = client.servers[server_name].reset_password - it '#reset_password' do - expect { client.servers[2].reset_password }.to raise_error(Hcloud::Error::Locked) - sleep(0.5) - action, pass = nil - expect { action, pass = client.servers[2].reset_password }.not_to raise_error expect(action).to be_a Hcloud::Action - expect(action.command).to eq('reset_password') - expect(action.status).to eq('running') - expect(pass).to eq('test123') + expect(action.command).to eq('reset_server_password') + expect(pass).to be_a String + + wait_for_action(client.servers[server_name], action.id) end it '#request_console' do - expect { client.servers[2].request_console }.to raise_error(Hcloud::Error::Locked) - sleep(0.5) - action, url, pass = nil - expect { action, url, pass = client.servers[2].request_console }.not_to raise_error + action, url, pass = client.servers[server_name].request_console + expect(action).to be_a Hcloud::Action expect(action.command).to eq('request_console') - expect(action.status).to eq('running') - expect(url).to eq("wss://web-console.hetzner.cloud/?server_id=#{client.servers[2].id}&token=token") - expect(pass).to eq('test123') + expect(url).to match(%r{wss://.+}) + expect(pass).to be_a String + + wait_for_action(client.servers[server_name], action.id) end it '#enable_rescue' do - expect { client.servers[2].enable_rescue(type: 'moo') }.to( + expect { client.servers[server_name].enable_rescue(type: 'moo') }.to( raise_error(Hcloud::Error::InvalidInput) ) - expect { client.servers[2].enable_rescue }.to( - raise_error(Hcloud::Error::Locked) - ) - sleep(0.5) - action, pass = nil - expect { action, pass = client.servers[2].enable_rescue(type: 'linux32') }.not_to raise_error + + action, pass = client.servers[server_name].enable_rescue(type: 'linux32') expect(action).to be_a Hcloud::Action expect(action.command).to eq('enable_rescue') - expect(action.status).to eq('running') - expect(pass).to eq('test123') + expect(pass).to be_a String + + wait_for_action(client.servers[server_name], action.id) end it '#change_protection' do - expect(client.servers[2]).to be_a Hcloud::Server - expect(client.servers[2].protection).to be_a Hash - expect(client.servers[2].protection['delete']).to be false - expect(client.servers[2].protection['rebuild']).to be false + expect(client.servers[server_name].protection).to be_a Hash + expect(client.servers[server_name].protection['delete']).to be false + expect(client.servers[server_name].protection['rebuild']).to be false - expect(client.servers[2].change_protection).to be_a Hcloud::Action + expect(client.servers[server_name].change_protection(rebuild: true, delete: true)).to \ + be_a Hcloud::Action - expect(client.servers[2].protection).to be_a Hash - expect(client.servers[2].protection['delete']).to be false + expect(client.servers[server_name].protection).to be_a Hash + expect(client.servers[server_name].protection['delete']).to be true - expect(client.servers[2].change_protection(delete: true)).to be_a Hcloud::Action - - expect(client.servers[2].protection).to be_a Hash - expect(client.servers[2].protection['delete']).to be true + # unprotect to allow deletion later + client.servers[server_name].change_protection(rebuild: false, delete: false) end it '#create_image' do - expect(client.servers[2]).to be_a Hcloud::Server - action, image = client.servers[2].create_image(description: 'test image', type: 'snapshot') - expect(image.description).to eq('test image') + image_name = resource_name('server-image') + + expect(client.servers[server_name]).to be_a Hcloud::Server + action, image = client.servers[server_name].create_image( + description: image_name, type: 'snapshot' + ) + expect(image.description).to eq(image_name) expect(image.type).to eq('snapshot') expect(action.command).to eq('create_image') + + wait_for_action(client.servers[server_name], action.id) + + # delete the image again + client.images[image.id].destroy + end + + it '#poweroff' do + action = client.servers[server_name].poweroff + + expect(action).to be_a Hcloud::Action + expect(action.command).to eq('stop_server') + + wait_for_action(client.servers[server_name], action.id) + + expect(client.servers[server_name].status).to eq('off') + end + + it '#poweron' do + action = client.servers[server_name].poweron + + expect(action).to be_a Hcloud::Action + expect(action.command).to eq('start_server') + + wait_for_action(client.servers[server_name], action.id) + + expect(client.servers[server_name].status).to eq('running') + end + + it '#destroy' do + [server_name, resource_name('server2'), resource_name('server3')].each do |name| + id = client.servers[name].id + expect { client.servers[id].destroy }.not_to raise_error + expect(client.servers[id]).to be nil + end end end diff --git a/spec/integration/server_type_spec.rb b/spec/integration/server_type_spec.rb index e4daf2d4..44c27f98 100644 --- a/spec/integration/server_type_spec.rb +++ b/spec/integration/server_type_spec.rb @@ -2,13 +2,9 @@ require 'spec_helper' -describe 'ServerType' do - let :client do - Hcloud::Client.new(token: 'secure') - end - +describe 'ServerType', :integration do it 'fetch server_types' do - expect(client.server_types.count).to eq(1) + expect(client.server_types.count).to be_an(Integer).and be > 0 end it '#[] -> find by id' do @@ -25,7 +21,7 @@ end it '#[] -> find by id, handle nonexistent' do - expect(client.ssh_keys[0]).to be nil + expect(client.server_types[0]).to be nil end it '#find -> find by id' do @@ -56,6 +52,6 @@ end it '#[] -> filter by name, handle nonexistent' do - expect(client.ssh_keys['mooo']).to be nil + expect(client.ssh_keys[nonexistent_name]).to be nil end end diff --git a/spec/integration/ssh_key_spec.rb b/spec/integration/ssh_key_spec.rb index c3d2ecf4..becc3e5e 100644 --- a/spec/integration/ssh_key_spec.rb +++ b/spec/integration/ssh_key_spec.rb @@ -4,13 +4,14 @@ REAL_KEY = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILh8GH'\ 'JkJRgf3wuuUUQYG3UfqtVK56+FEXAOFaNZ659C m@x.com' +REAL_KEY_2 = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILh8GH'\ + 'JkJRgf3wuuUUQYG3UfqtVK56+FEXAOFaNZ659D m@x.com' + +describe 'SSHKey', :integration do + let(:key_name) { resource_name('key') } -describe 'SSHKey' do - let :client do - Hcloud::Client.new(token: 'secure') - end it 'fetch ssh_keys' do - expect(client.ssh_keys.count).to eq(0) + expect(client.ssh_keys.count).to be_a Integer end it 'create new ssh_key, handle missing name' do @@ -20,29 +21,29 @@ end it 'create new ssh_key, handle missing public_key' do - expect { client.ssh_keys.create(name: 'moo', public_key: nil) }.to( + expect { client.ssh_keys.create(name: key_name, public_key: nil) }.to( raise_error(Hcloud::Error::InvalidInput) ) end it 'create new ssh_key, handle invalid public_key' do - expect { client.ssh_keys.create(name: 'moo', public_key: 'not-ssh') }.to( + expect { client.ssh_keys.create(name: key_name, public_key: 'not-ssh') }.to( raise_error(Hcloud::Error::InvalidInput) ) end it 'create new ssh_key' do - key = client.ssh_keys.create(name: 'moo', public_key: REAL_KEY, labels: { 'source' => 'test' }) + key = client.ssh_keys.create(name: key_name, public_key: REAL_KEY, labels: { 'source' => 'test' }) expect(key).to be_a Hcloud::SSHKey expect(key.id).to be_a Integer - expect(key.name).to eq('moo') + expect(key.name).to eq(key_name) expect(key.fingerprint.split(':').count).to eq(16) expect(key.public_key).to eq(REAL_KEY) expect(key.labels).to eq({ 'source' => 'test' }) end it 'create new ssh_key, uniq name' do - expect { client.ssh_keys.create(name: 'moo', public_key: 'ssh-rsa') }.to( + expect { client.ssh_keys.create(name: key_name, public_key: REAL_KEY_2) }.to( raise_error(Hcloud::Error::UniquenessError) ) end @@ -54,7 +55,7 @@ end it 'fetch ssh_keys' do - expect(client.ssh_keys.count).to eq(1) + expect(client.ssh_keys.count).to be_an(Integer).and be > 0 end it '#[] -> find by id' do @@ -62,8 +63,8 @@ id = client.ssh_keys.first.id expect(id).to be_a Integer expect(client.ssh_keys[id]).to be_a Hcloud::SSHKey - expect(client.ssh_keys[id].name).to eq('moo') - expect(client.ssh_keys[id].public_key).to eq(REAL_KEY) + expect(client.ssh_keys[id].name).to be_a String + expect(client.ssh_keys[id].public_key).to be_a String expect(client.ssh_keys[id].fingerprint.split(':').count).to eq(16) end @@ -76,8 +77,8 @@ id = client.ssh_keys.first.id expect(id).to be_a Integer expect(client.ssh_keys.find(id)).to be_a Hcloud::SSHKey - expect(client.ssh_keys.find(id).name).to eq('moo') - expect(client.ssh_keys.find(id).public_key).to eq(REAL_KEY) + expect(client.ssh_keys.find(id).name).to be_a String + expect(client.ssh_keys.find(id).public_key).to be_a String expect(client.ssh_keys.find(id).fingerprint.split(':').count).to eq(16) end @@ -86,29 +87,31 @@ end it '#[] -> filter by name' do - expect(client.ssh_keys['moo']).to be_a Hcloud::SSHKey - expect(client.ssh_keys['moo'].name).to eq('moo') - expect(client.ssh_keys['moo'].public_key).to eq(REAL_KEY) - expect(client.ssh_keys['moo'].fingerprint.split(':').count).to eq(16) + expect(client.ssh_keys[key_name]).to be_a Hcloud::SSHKey + expect(client.ssh_keys[key_name].name).to eq(key_name) + expect(client.ssh_keys[key_name].public_key).to eq(REAL_KEY) + expect(client.ssh_keys[key_name].fingerprint.split(':').count).to eq(16) end it '#[] -> filter by name, handle nonexistent' do - expect(client.ssh_keys['mooo']).to be nil + expect(client.ssh_keys[nonexistent_name]).to be nil end it '#update' do + new_name = resource_name('key-new') + expect(client.ssh_keys.first).to be_a Hcloud::SSHKey - id = client.ssh_keys.first.id + id = client.ssh_keys[key_name].id expect(id).to be_a Integer - expect(client.ssh_keys.find(id).name).to eq('moo') + expect(client.ssh_keys.find(id).name).to eq(key_name) updated = client.ssh_keys.find(id).update( - name: 'hui', + name: new_name, labels: { 'source' => 'unittest' } ) - expect(updated.name).to eq('hui') + expect(updated.name).to eq(new_name) expect(updated.labels['source']).to eq('unittest') - expect(client.ssh_keys.find(id).name).to eq('hui') + expect(client.ssh_keys.find(id).name).to eq(new_name) expect(client.ssh_keys.find(id).labels['source']).to eq('unittest') end @@ -119,10 +122,9 @@ end it '#destroy' do - expect(client.ssh_keys.first).to be_a Hcloud::SSHKey - id = client.ssh_keys.first.id - expect(id).to be_a Integer - expect(client.ssh_keys.find(id).destroy).to be_a Hcloud::SSHKey - expect(client.ssh_keys[id]).to be nil + to_delete = client.ssh_keys[resource_name('key-new')] + expect(to_delete).to be_a Hcloud::SSHKey + expect(client.ssh_keys.find(to_delete.id).destroy).to be_a Hcloud::SSHKey + expect(client.ssh_keys[to_delete.id]).to be nil end end diff --git a/spec/integration/z_floating_ip_spec.rb b/spec/integration/z_floating_ip_spec.rb deleted file mode 100644 index 83a64bd6..00000000 --- a/spec/integration/z_floating_ip_spec.rb +++ /dev/null @@ -1,162 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe 'FloatingIP' do - let :client do - Hcloud::Client.new(token: 'secure') - end - - it 'fetch floating ips' do - expect(client.floating_ips.count).to eq(1) - end - - it '#[] -> find by id' do - expect(client.floating_ips.first).to be_a Hcloud::FloatingIP - id = client.floating_ips.first.id - expect(id).to be_a Integer - expect(client.floating_ips[id]).to be_a Hcloud::FloatingIP - expect(client.floating_ips[id].description).to be_a String - expect(client.floating_ips[id].type).to be_a String - expect(client.floating_ips[id].ip).to be_a String - expect(client.floating_ips[id].server).to be nil - expect(client.floating_ips[id].blocked).to be false - expect(client.floating_ips[id].home_location.id).to eq(2) - expect(client.floating_ips[id].created).to be_a Time - end - - it '#create -> invalid type' do - expect do - client.floating_ips.create(type: 'moo', home_location: 'nbg1') - end.to raise_error(Hcloud::Error::InvalidInput) - expect do - client.floating_ips.create(type: 'moo', home_location: 'nbg1', server: 1) - end.to raise_error(Hcloud::Error::InvalidInput) - end - - it '#create -> invalid home_location' do - expect do - client.floating_ips.create(type: 'ipv4', home_location: 'nbg2') - end.to raise_error(Hcloud::Error::InvalidInput) - expect do - client.floating_ips.create(type: 'ipv4', home_location: 'nbg2', server: 1) - end.to raise_error(Hcloud::Error::InvalidInput) - end - - it '#create -> invalid server' do - expect do - client.floating_ips.create(type: 'ipv4', home_location: 'nbg1', server: 'hui') - end.to raise_error(Hcloud::Error::InvalidInput) - end - - it '#create(type: ipv4, server: 1)' do - a, f = nil - expect do - a, f = client.floating_ips.create( - type: 'ipv4', server: 1, labels: { 'source' => 'create' } - ) - end.not_to raise_error - expect(a).to be_a Hcloud::Action - expect(a.status).to eq('running') - expect(a.command).to eq('assign_floating_ip') - expect(f).to be_a Hcloud::FloatingIP - expect(f.ip).to eq('127.0.0.2') - expect(f.blocked).to be false - expect(f.home_location.name).to eq('nbg1') - expect(f.labels).to eq({ 'source' => 'create' }) - expect(f.actions.count).to eq(1) - expect(f.actions.first.command).to eq('assign_floating_ip') - end - - it "#create(type: ipv4, server: 1, home_location: 'fsn1')" do - a, f = nil - expect do - a, f = client.floating_ips.create(type: 'ipv4', home_location: 'fsn1', server: 1) - end.not_to raise_error - expect(a).to be_a Hcloud::Action - expect(a.status).to eq('running') - expect(a.command).to eq('assign_floating_ip') - expect(f).to be_a Hcloud::FloatingIP - expect(f.ip).to eq('127.0.0.2') - expect(f.blocked).to be false - expect(f.home_location.name).to eq('fsn1') - end - - it '#create(type: ipv4, home_location: nbg1)' do - a, f = nil - expect do - a, f = client.floating_ips.create(type: 'ipv4', home_location: 'nbg1') - end.not_to raise_error - expect(a).to be nil - expect(f).to be_a Hcloud::FloatingIP - expect(f.ip).to eq('127.0.0.2') - expect(f.blocked).to be false - expect(f.home_location.name).to eq('nbg1') - end - - it "#update(description: 'moo')" do - expect(client.floating_ips.find(1).description).to be nil - expect { client.floating_ips.find(1).update(description: 'moo') }.not_to raise_error - expect(client.floating_ips.find(1).description).to eq('moo') - end - - it '#update(labels:)' do - ip = client.floating_ips.find(1) - updated = ip.update(labels: { 'source' => 'update' }) - expect(updated.labels).to eq({ 'source' => 'update' }) - expect(client.floating_ips.find(1).labels).to eq({ 'source' => 'update' }) - end - - it '#where -> find by label_selector' do - ips = client.floating_ips.where(label_selector: 'source=update').to_a - expect(ips.length).to eq(1) - expect(ips.first.labels).to include('source' => 'update') - end - - it '#assign(server: 999)' do - expect(client.floating_ips.find(3).server).to be nil - expect(client.floating_ips.find(3).assign(server: 999)).to be_a Hcloud::Action - expect(client.floating_ips.find(3).server).to eq(999) - end - - it '#unassign()' do - expect(client.floating_ips.find(3).server).to eq(999) - expect(client.floating_ips.find(3).unassign).to be_a Hcloud::Action - expect(client.floating_ips.find(3).server).to be nil - end - - it '#change_dns_ptr' do - expect(client.floating_ips.first).to be_a Hcloud::FloatingIP - expect(client.floating_ips.first.dns_ptr.count).to eq(1) - expect(client.floating_ips.first.dns_ptr[0]['ip']).to eq('127.0.0.1') - expect(client.floating_ips.first.dns_ptr[0]['dns_ptr']).to eq('static.1.0.0.127.clients.your-server.de') - - expect(client.floating_ips.first.change_dns_ptr(ip: '127.0.0.1', dns_ptr: 'moo')).to be_a Hcloud::Action - - expect(client.floating_ips.first.dns_ptr.count).to eq(1) - expect(client.floating_ips.first.dns_ptr[0]['ip']).to eq('127.0.0.1') - expect(client.floating_ips.first.dns_ptr[0]['dns_ptr']).to eq('moo') - end - - it '#change_protection' do - expect(client.floating_ips.first).to be_a Hcloud::FloatingIP - expect(client.floating_ips.first.protection).to be_a Hash - expect(client.floating_ips.first.protection['delete']).to be false - - expect(client.floating_ips.first.change_protection).to be_a Hcloud::Action - - expect(client.floating_ips.first.protection).to be_a Hash - expect(client.floating_ips.first.protection['delete']).to be false - - expect(client.floating_ips.first.change_protection(delete: true)).to be_a Hcloud::Action - - expect(client.floating_ips.first.protection).to be_a Hash - expect(client.floating_ips.first.protection['delete']).to be true - end - - it '#destroy()' do - expect(client.floating_ips.count).to eq(4) - expect { client.floating_ips.find(595).destroy }.not_to raise_error - expect(client.floating_ips.count).to eq(3) - end -end diff --git a/spec/integration/zz_image_spec.rb b/spec/integration/zz_image_spec.rb deleted file mode 100644 index f07e617e..00000000 --- a/spec/integration/zz_image_spec.rb +++ /dev/null @@ -1,184 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe 'Image' do - let :client do - Hcloud::Client.new(token: 'secure') - end - - it 'fetch images' do - expect(client.images.count).to eq(2) - end - - it '#[] -> find by id (system image)' do - expect(client.images.first).to be_a Hcloud::Image - id = client.images.first.id - expect(id).to be_a Integer - expect(client.images[id]).to be_a Hcloud::Image - expect(client.images[id].name).to be_a String - expect(client.images[id].type).to be_a String - expect(client.images[id].status).to be_a String - expect(client.images[id].name).to be_a String - expect(client.images[id].description).to be_a String - expect(client.images[id].created_from).to be nil - expect(client.images[id].created).to be_a Time - expect(client.images[id].bound_to).to be nil - expect(client.images[id].os_flavor).to be_a String - expect(client.images[id].os_version).to be_a String - expect(client.images[id].rapid_deploy).to be true - expect(client.images[id].deprecated).to be_a Time - end - - it '#[] -> find by id (snapshot image)' do - expect(client.images[3454]).to be_a Hcloud::Image - expect(client.images[3454].name).to be nil - expect(client.images[3454].type).to be_a String - expect(client.images[3454].status).to be_a String - expect(client.images[3454].name).to be nil - expect(client.images[3454].description).to be_a String - expect(client.images[3454].created_from).to be_a Hash - expect(client.images[3454].created).to be_a Time - expect(client.images[3454].bound_to).to be nil - expect(client.images[3454].os_flavor).to be_a String - expect(client.images[3454].os_version).to be nil - expect(client.images[3454].rapid_deploy).to be false - expect(client.images[3454].deprecated).to be nil - end - - it '#update(description:) - handle nil' do - expect { client.images[3454].update(description: nil) }.not_to( - raise_error - ) - end - - it '#update(description:)' do - expect(client.images[3454].description).not_to eq('test123') - expect(client.images[3454].update(description: 'test123').description).to eq('test123') - expect(client.images[3454].description).to eq('test123') - end - - it '#update(type:) - handle invalid' do - expect { client.images.first.update(type: 'moo') }.to( - raise_error(Hcloud::Error::InvalidInput) - ) - end - - it '#update(type:) - handle nil' do - expect { client.images.first.update(type: nil) }.not_to( - raise_error - ) - end - - it '#update(type:) - handle backup convert for predefinded images' do - expect { client.images.first.update(type: 'backup') }.to( - raise_error(Hcloud::Error::NotFound) - ) - end - - it '#update(labels:)' do - image = client.images[3454] - updated = image.update(labels: { 'source' => 'update' }) - expect(updated.labels).to eq({ 'source' => 'update' }) - expect(client.images[3454].labels).to eq({ 'source' => 'update' }) - end - - it '#where -> find by label_selector' do - images = client.images.where(label_selector: 'source=update').to_a - expect(images.length).to eq(1) - expect(images.first.labels).to include('source' => 'update') - end - - it '#to_snapshot' do - expect(client.images[3454].description).to eq('test123') - expect(client.images[3454].to_snapshot).to be_a Hcloud::Image - end - - it '#where(name:)' do - expect(client.images.where(name: 'moo').count).to eq(0) - expect(client.images.where(name: 'ubuntu-16.04').count).to eq(1) - x = client.images.where(name: 'ubuntu-16.04').first - expect(x).to be_a Hcloud::Image - expect(x.name).to be_a String - expect(x.type).to be_a String - expect(x.status).to be_a String - expect(x.name).to be_a String - expect(x.description).to be_a String - expect(x.created_from).to be nil - expect(x.created).to be_a Time - expect(x.bound_to).to be nil - expect(x.os_flavor).to be_a String - expect(x.os_version).to be_a String - expect(x.rapid_deploy).to be true - expect(x.deprecated).to be_a Time - end - - it '#[] -> find by id, handle nonexistent' do - expect(client.images[0]).to be nil - end - - it '#find -> find by id' do - expect(client.images.first).to be_a Hcloud::Image - id = client.images.first.id - expect(id).to be_a Integer - expect(client.images.find(id)).to be_a Hcloud::Image - expect(client.images.find(id).name).to be_a String - expect(client.images.find(id).type).to be_a String - expect(client.images.find(id).status).to be_a String - expect(client.images.find(id).name).to be_a String - expect(client.images.find(id).description).to be_a String - expect(client.images.find(id).created_from).to be nil - expect(client.images.find(id).created).to be_a Time - expect(client.images.find(id).bound_to).to be nil - expect(client.images.find(id).os_flavor).to be_a String - expect(client.images.find(id).os_version).to be_a String - expect(client.images.find(id).rapid_deploy).to be true - expect(client.images.find(id).deprecated).to be_a Time - end - - it '#find -> find by id, handle nonexistent' do - expect { client.images.find(0).id }.to raise_error(Hcloud::Error::NotFound) - end - - it '#[] -> filter by name' do - expect(client.images['ubuntu-16.04']).to be_a Hcloud::Image - expect(client.images['ubuntu-16.04'].name).to be_a String - expect(client.images['ubuntu-16.04'].type).to be_a String - expect(client.images['ubuntu-16.04'].status).to be_a String - expect(client.images['ubuntu-16.04'].name).to be_a String - expect(client.images['ubuntu-16.04'].description).to be_a String - expect(client.images['ubuntu-16.04'].created_from).to be nil - expect(client.images['ubuntu-16.04'].created).to be_a Time - expect(client.images['ubuntu-16.04'].bound_to).to be nil - expect(client.images['ubuntu-16.04'].os_flavor).to be_a String - expect(client.images['ubuntu-16.04'].os_version).to be_a String - expect(client.images['ubuntu-16.04'].rapid_deploy).to be true - expect(client.images['ubuntu-16.04'].deprecated).to be_a Time - end - - it '#[] -> filter by name, handle nonexistent' do - expect(client.images['mooo']).to be nil - end - - it '#change_protection' do - expect(client.images.first).to be_a Hcloud::Image - expect(client.images.first.protection).to be_a Hash - expect(client.images.first.protection['delete']).to be false - - expect(client.images.first.change_protection).to be_a Hcloud::Action - - expect(client.images.first.protection).to be_a Hash - expect(client.images.first.protection['delete']).to be false - - expect(client.images.first.change_protection(delete: true)).to be_a Hcloud::Action - - expect(client.images.first.protection).to be_a Hash - expect(client.images.first.protection['delete']).to be true - end - - it '#destroy()' do - expect(client.images.count).to eq(2) - expect { client.images[3454].destroy }.not_to raise_error - expect(client.images.count).to eq(1) - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e4d33726..2496eb98 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,14 +5,19 @@ require 'pry' require 'simplecov' -SimpleCov.start +SimpleCov.start do + # integration tests are only run manually and thus these files will + # not be part of the automatic codecov report + add_filter 'spec/integration' + add_filter 'spec/support/integration.rb' +end require 'codecov' SimpleCov.formatter = SimpleCov::Formatter::Codecov -require_relative './fake_service/base' require_relative './doubles/base' require_relative './doubles/action_tests' +require_relative './support/integration' require_relative './support/typhoeus_ext' require_relative './support/matchers' @@ -35,17 +40,5 @@ def deep_load(scope) Faker::Config.random = Random.new(c.seed) c.include_context 'test doubles', :doubles - - if ENV['LEGACY_TESTS'] - require 'webmock/rspec' - c.before(:each) do - stub_request(:any, /api.hetzner.cloud/).to_rack(Hcloud::FakeService::Base) - end - - c.after(:all) do - # Action holds the record of all executed actions (which can be queried - # on the API). We need to delete this record after each example. - Hcloud::FakeService::Action.reset - end - end + c.include_context 'integration auth', :integration end diff --git a/spec/support/integration.rb b/spec/support/integration.rb new file mode 100644 index 00000000..1fbf1cef --- /dev/null +++ b/spec/support/integration.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.shared_context 'integration auth' do + # Some tests require a server or other resource other from the tested resource type. + # We provide such resources as "helper". + let(:helper_name) { resource_name('helper') } + + let :nonexistent_name do + # just some randomly generated UUID + '9e6c837e-b075-43c2-a7cb-44fd4bd8bc32' + end + + before(:context, integration_helper: :server) do + client = Hcloud::Client.new(token: ENV['HCLOUD_TOKEN']) + action, * = client.servers.create( + name: resource_name('helper'), server_type: 'cx21', image: 'ubuntu-22.04' + ) + + server = client.servers[resource_name('helper')] + + wait_for_action(server, action.id) + sleep 1 while server.locked + end + + after(:context, integration_helper: :server) do + client = Hcloud::Client.new(token: ENV['HCLOUD_TOKEN']) + client.servers[resource_name('helper')]&.destroy + end + + let :client do + Hcloud::Client.new(token: ENV['HCLOUD_TOKEN']) + end + + def resource_name(name) + # use a random ID to allow multiple CI runs in parallel + $random_resource_name_id ||= Random.rand(10_000..99_999) + "hcloud-ruby-integration-#{$random_resource_name_id}-#{name}" + end + + def wait_for_action(resource, action_id) + sleep 1 until resource.actions[action_id].status == 'success' + end +end