foreman_docker 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +22 -22
  3. data/app/assets/javascripts/foreman_docker/image_step.js +36 -6
  4. data/app/controllers/api/v2/containers_controller.rb +13 -11
  5. data/app/controllers/containers/steps_controller.rb +8 -2
  6. data/app/controllers/containers_controller.rb +4 -3
  7. data/app/controllers/image_search_controller.rb +36 -79
  8. data/app/helpers/container_steps_helper.rb +21 -0
  9. data/app/models/concerns/foreman_docker/parameter_validators.rb +27 -4
  10. data/app/models/container.rb +9 -10
  11. data/app/models/docker_container_wizard_state.rb +2 -0
  12. data/app/models/docker_container_wizard_states/dns.rb +2 -8
  13. data/app/models/docker_container_wizard_states/environment.rb +4 -14
  14. data/app/models/docker_container_wizard_states/environment_variable.rb +2 -2
  15. data/app/models/docker_container_wizard_states/exposed_port.rb +2 -8
  16. data/app/models/docker_container_wizard_states/image.rb +30 -0
  17. data/app/models/docker_container_wizard_states/preliminary.rb +1 -1
  18. data/app/models/docker_parameter.rb +20 -0
  19. data/app/models/docker_registry.rb +6 -4
  20. data/app/models/environment_variable.rb +3 -3
  21. data/app/models/exposed_port.rb +4 -10
  22. data/app/models/foreman_docker/compute_resource_extensions.rb +11 -0
  23. data/app/models/foreman_docker/dns.rb +3 -9
  24. data/app/models/foreman_docker/docker.rb +1 -5
  25. data/app/models/service/containers.rb +15 -11
  26. data/app/models/service/registry_api.rb +87 -15
  27. data/app/services/foreman_docker/image_search.rb +92 -0
  28. data/app/views/containers/index.html.erb +1 -3
  29. data/app/views/containers/show.html.erb +1 -1
  30. data/app/views/containers/steps/_image_hub_tab.html.erb +39 -29
  31. data/app/views/containers/steps/_title.html.erb +1 -1
  32. data/app/views/containers/steps/preliminary.html.erb +1 -1
  33. data/app/views/foreman_docker/common_parameters/_dns_entry.html.erb +1 -1
  34. data/app/views/foreman_docker/common_parameters/_environment_variable.html.erb +1 -1
  35. data/app/views/foreman_docker/common_parameters/_exposed_port.html.erb +1 -1
  36. data/app/views/image_search/_repository_search_results.html.erb +1 -1
  37. data/app/views/registries/index.html.erb +1 -3
  38. data/app/views/registries/new.html.erb +1 -1
  39. data/db/migrate/20160605133025_create_docker_parameters.rb +13 -0
  40. data/db/migrate/20160605134652_move_parameters_to_docker_parameters.rb +27 -0
  41. data/lib/foreman_docker/engine.rb +6 -7
  42. data/lib/foreman_docker/version.rb +1 -1
  43. data/lib/tasks/test.rake +35 -0
  44. data/test/functionals/api/v2/containers_controller_test.rb +21 -0
  45. data/test/functionals/api/v2/registries_controller_test.rb +4 -3
  46. data/test/functionals/containers_controller_test.rb +5 -0
  47. data/test/functionals/containers_steps_controller_test.rb +74 -36
  48. data/test/functionals/image_search_controller_test.rb +166 -12
  49. data/test/integration/container_test.rb +1 -1
  50. data/test/test_plugin_helper.rb +10 -0
  51. data/test/units/container_test.rb +1 -1
  52. data/test/units/containers_service_test.rb +31 -9
  53. data/test/units/docker_container_wizard_states/image_test.rb +84 -0
  54. data/test/units/docker_registry_test.rb +27 -10
  55. data/test/units/foreman_docker/compute_resource_extensions_test.rb +10 -0
  56. data/test/units/image_search_service_test.rb +188 -0
  57. data/test/units/registry_api_test.rb +206 -12
  58. metadata +55 -36
  59. data/lib/foreman_docker/tasks/test.rake +0 -45
  60. data/locale/de/foreman_docker.edit.po +0 -631
  61. data/locale/de/foreman_docker.po.time_stamp +0 -0
  62. data/locale/es/foreman_docker.edit.po +0 -631
  63. data/locale/es/foreman_docker.po.time_stamp +0 -0
  64. data/locale/fr/foreman_docker.edit.po +0 -632
  65. data/locale/fr/foreman_docker.po.time_stamp +0 -0
  66. data/locale/it/foreman_docker.edit.po +0 -630
  67. data/locale/it/foreman_docker.po.time_stamp +0 -0
  68. data/locale/ja/foreman_docker.edit.po +0 -634
  69. data/locale/ja/foreman_docker.po.time_stamp +0 -0
  70. data/locale/ko/foreman_docker.edit.po +0 -629
  71. data/locale/ko/foreman_docker.po.time_stamp +0 -0
  72. data/locale/pt_BR/foreman_docker.edit.po +0 -631
  73. data/locale/pt_BR/foreman_docker.po.time_stamp +0 -0
  74. data/locale/ru/foreman_docker.edit.po +0 -630
  75. data/locale/ru/foreman_docker.po.time_stamp +0 -0
  76. data/locale/zh_CN/foreman_docker.edit.po +0 -628
  77. data/locale/zh_CN/foreman_docker.po.time_stamp +0 -0
  78. data/locale/zh_TW/foreman_docker.edit.po +0 -628
  79. data/locale/zh_TW/foreman_docker.po.time_stamp +0 -0
@@ -0,0 +1,84 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module DockerContainerWizardStates
4
+ class ImageTest < ActiveSupport::TestCase
5
+ let(:image) { 'centos' }
6
+ let(:tags) { ['latest', '5', '4.3'] }
7
+ let(:docker_hub) { Service::RegistryApi.new(url: 'https://nothub.com') }
8
+ let(:registry) { FactoryGirl.create(:docker_registry) }
9
+ let(:compute_resource) { FactoryGirl.create(:docker_cr) }
10
+ let(:image_search_service) { ForemanDocker::ImageSearch.new }
11
+ let(:wizard_state) do
12
+ DockerContainerWizardState.create
13
+ end
14
+ let(:preliminary) do
15
+ DockerContainerWizardStates::Preliminary.create(
16
+ compute_resource: compute_resource,
17
+ wizard_state: wizard_state
18
+ )
19
+ end
20
+
21
+ subject do
22
+ Image.new(
23
+ repository_name: image,
24
+ tag: tags.first,
25
+ wizard_state: wizard_state
26
+ )
27
+ end
28
+
29
+ setup do
30
+ ForemanDocker::ImageSearch.any_instance.unstub(:available?)
31
+ wizard_state.preliminary = preliminary
32
+ Service::RegistryApi.stubs(:docker_hub).returns(docker_hub)
33
+ end
34
+
35
+
36
+ describe 'it validates that the image is available' do
37
+ test 'validates via the image_search_service' do
38
+ DockerContainerWizardStates::Image.any_instance
39
+ .stubs(:image_search_service)
40
+ .returns(image_search_service)
41
+ available = [true, false].sample
42
+ image_search_service.expects(:available?)
43
+ .with("#{image}:#{tags.first}").at_least_once
44
+ .returns(available)
45
+ assert_equal available, subject.valid?
46
+ end
47
+
48
+ context 'when no registy is set' do
49
+ test 'it queries the compute_resource and docker_hub' do
50
+ compute_resource.expects(:image).with(image).at_least_once
51
+ .returns(image)
52
+ compute_resource.expects(:tags_for_local_image).at_least_once
53
+ .with(image, tags.first)
54
+ .returns([])
55
+ docker_hub.expects(:tags).at_least_once
56
+ .returns([])
57
+
58
+ subject.validate
59
+ end
60
+ end
61
+
62
+ context 'when a registy is set' do
63
+ setup do
64
+ subject.stubs(:registry_id).returns(registry.id)
65
+ DockerRegistry.expects(:find).with(registry.id)
66
+ .returns(registry)
67
+ end
68
+
69
+ test 'it queries the compute_resource and registry' do
70
+ compute_resource.expects(:image).with(image).at_least_once
71
+ .returns(image)
72
+ compute_resource.expects(:tags_for_local_image).at_least_once
73
+ .with(image, tags.first)
74
+ .returns([])
75
+ docker_hub.expects(:tags).never
76
+ registry.api.expects(:tags).with(image, tags.first).at_least_once
77
+ .returns([])
78
+
79
+ subject.validate
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,6 +1,8 @@
1
1
  require 'test_plugin_helper'
2
2
 
3
3
  class DockerRegistryTest < ActiveSupport::TestCase
4
+ subject { FactoryGirl.create(:docker_registry) }
5
+
4
6
  test 'used_location_ids should return correct location ids' do
5
7
  location = FactoryGirl.build(:location)
6
8
  r = as_admin do
@@ -29,21 +31,36 @@ class DockerRegistryTest < ActiveSupport::TestCase
29
31
  should validate_uniqueness_of(:name)
30
32
  should validate_uniqueness_of(:url)
31
33
 
32
- context 'attempt to login' do
34
+ describe 'registry validation' do
33
35
  setup do
34
- @registry = FactoryGirl.build(:docker_registry)
35
- @registry.unstub(:attempt_login)
36
+ subject.unstub(:attempt_login)
37
+ end
38
+
39
+ test 'is valid when the api is ok' do
40
+ subject.api.expects(:ok?).returns(true)
41
+ assert subject.valid?
36
42
  end
37
43
 
38
- test 'before creating a registry' do
39
- RestClient::Resource.any_instance.expects(:get)
40
- assert @registry.valid?
44
+ test 'is not valid when api is not ok' do
45
+ subject.api.expects(:ok?)
46
+ .raises(Docker::Error::AuthenticationError)
47
+ refute subject.valid?
41
48
  end
49
+ end
50
+
51
+ describe '#api' do
52
+ let(:api) { subject.api }
53
+
54
+ test 'returns a RegistryApi instance' do
55
+ assert_kind_of Service::RegistryApi, api
56
+ end
57
+ end
58
+
59
+ describe '#api' do
60
+ let(:api) { subject.api }
42
61
 
43
- test 'display errors in case authentication failed' do
44
- RestClient::Resource.any_instance.expects(:get).
45
- raises(Docker::Error::AuthenticationError)
46
- refute @registry.valid?
62
+ test 'returns a RegistryApi instance' do
63
+ assert_kind_of Service::RegistryApi, api
47
64
  end
48
65
  end
49
66
  end
@@ -0,0 +1,10 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module ForemanDocker
4
+ class ComputeResourceExtensionsTest < ActiveSupport::TestCase
5
+ test 'ComputeResource::providers_requiring_url returns expected providers' do
6
+ expected_providers = "Docker, Libvirt, oVirt, OpenStack and Rackspace"
7
+ assert_equal expected_providers, ComputeResource.providers_requiring_url
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,188 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class ImageSearchServiceTest < ActiveSupport::TestCase
4
+ let(:compute_resource) { FactoryGirl.create(:docker_cr) }
5
+ let(:registry) { FactoryGirl.create(:docker_registry).api }
6
+ let(:term) { 'centos' }
7
+ let(:query) { { term: term, tags: 'false' } }
8
+
9
+ subject { ForemanDocker::ImageSearch.new(compute_resource, registry) }
10
+
11
+ setup do
12
+ stub_registry_api
13
+ end
14
+
15
+ describe '#add_source' do
16
+ setup do
17
+ subject.instance_variable_set(:@sources, {})
18
+ end
19
+
20
+ test 'adds a compute resource to @sources[:compute_resource]' do
21
+ subject.add_source(compute_resource)
22
+ assert_equal compute_resource,
23
+ subject.instance_variable_get(:@sources)[:compute_resource].first
24
+ end
25
+
26
+ test 'adds a registry to @sources[:registry]' do
27
+ subject.add_source(registry)
28
+ assert_equal registry,
29
+ subject.instance_variable_get(:@sources)[:registry].first
30
+ end
31
+ end
32
+
33
+ describe '#remove_source' do
34
+ test 'removes a registry source from @sources' do
35
+ refute subject.instance_variable_get(:@sources)[:registry].empty?
36
+ subject.remove_source(registry)
37
+ assert subject.instance_variable_get(:@sources)[:registry].empty?
38
+ end
39
+
40
+ test 'removes a compute_resource source from @sources' do
41
+ refute subject.instance_variable_get(:@sources)[:compute_resource].empty?
42
+ subject.remove_source(compute_resource)
43
+ assert subject.instance_variable_get(:@sources)[:compute_resource].empty?
44
+ end
45
+ end
46
+
47
+ describe '#search' do
48
+ test 'returns {"name" => value } pairs' do
49
+ return_result = Hash.new
50
+ return_result.stubs(:info).returns({ 'RepoTags' => ["#{term}:latest"]})
51
+ compute_resource.stubs(:local_images).with(term)
52
+ .returns([return_result])
53
+ result = subject.search(query)
54
+ assert_equal({"name" => term}, result.first)
55
+ end
56
+
57
+ context 'tags is false' do
58
+ test 'calls #images with term as query' do
59
+ subject.expects(:images).with(term)
60
+ .returns([])
61
+ subject.search(query)
62
+ end
63
+ end
64
+
65
+ context 'tags is "true"' do
66
+ setup do
67
+ query[:tags] = 'true'
68
+ end
69
+
70
+ test 'calls #tags with term as query' do
71
+ subject.expects(:tags).with(term)
72
+ .returns([])
73
+ subject.search(query)
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '#images' do
79
+ context 'a compute_resource set' do
80
+ test 'calls #search_compute_resource with term as query' do
81
+ subject.expects(:compute_resource_search).with(compute_resource, term)
82
+ .returns([])
83
+ subject.images(term)
84
+ end
85
+ end
86
+
87
+ context 'no compute_resource is set' do
88
+ setup do
89
+ subject.remove_source(compute_resource)
90
+ end
91
+
92
+ test 'does not call #search_compute_resource' do
93
+ subject.expects(:compute_resource_search).with(compute_resource, term)
94
+ .never
95
+ subject.images(term)
96
+ end
97
+ end
98
+
99
+ context 'a registry is set' do
100
+ test 'calls #search_registry' do
101
+ subject.expects(:registry_search).with(registry, term)
102
+ .returns([])
103
+ subject.images(term)
104
+ end
105
+ end
106
+
107
+ context 'no registry is set' do
108
+ setup do
109
+ subject.remove_source(registry)
110
+ end
111
+
112
+ test 'does not call #search_registry' do
113
+ subject.expects(:registry_search).with(registry, term)
114
+ .never
115
+ subject.images(term)
116
+ end
117
+ end
118
+ end
119
+
120
+ describe '#tags' do
121
+ let(:tag) { 'latest' }
122
+ let(:query) { "#{term}:#{tag}" }
123
+
124
+ context 'a compute_resource set' do
125
+ test 'calls #compute_resource with image name and tag' do
126
+ subject.expects(:compute_resource_tags).with(compute_resource, term, tag)
127
+ .returns([])
128
+ subject.tags(query)
129
+ end
130
+ end
131
+
132
+ context 'no compute_resource is set' do
133
+ setup do
134
+ subject.remove_source(compute_resource)
135
+ end
136
+
137
+ test 'does not call #search_compute_resource' do
138
+ subject.expects(:compute_resource_tags).with(compute_resource, term, tag)
139
+ .never
140
+ subject.tags(query)
141
+ end
142
+ end
143
+
144
+ context 'a registry is set' do
145
+ setup do
146
+ subject.remove_source(compute_resource)
147
+ end
148
+
149
+ test 'calls #registry_tags with image name and tag' do
150
+ subject.expects(:registry_tags).with(registry, term, tag)
151
+ .returns([])
152
+ subject.tags(query)
153
+ end
154
+ end
155
+
156
+ context 'no registry is set' do
157
+ setup do
158
+ subject.remove_source(registry)
159
+ end
160
+
161
+ test 'does not call #registry_tags' do
162
+ subject.expects(:registry_search).with(registry, term, tag)
163
+ .never
164
+ subject.images(query)
165
+ end
166
+ end
167
+ end
168
+
169
+ describe '#available?' do
170
+ test 'calls #tags with query' do
171
+ subject.expects(:tags).with(query).once
172
+ .returns([])
173
+ subject.available?(query)
174
+ end
175
+
176
+ test 'returns true if any matching image and tag is found' do
177
+ subject.stubs(:tags).with(query)
178
+ .returns([{ 'name' => "#{term}:latest" }])
179
+ subject.available?(query)
180
+ end
181
+
182
+ test 'returns false if none are found' do
183
+ subject.stubs(:tags).with(query)
184
+ .returns([])
185
+ subject.available?(query)
186
+ end
187
+ end
188
+ end
@@ -1,17 +1,211 @@
1
1
  require 'test_plugin_helper'
2
2
 
3
3
  class RegistryApiTest < ActiveSupport::TestCase
4
- test "initialize handles username password info correctly" do
5
- uname = "tardis"
6
- password = "boo"
7
- url = "http://docker-who.gov"
8
- reg = Service::RegistryApi.new(:url => url,
9
- :user => uname,
10
- :password => password)
11
- assert reg.config[:url].include?(uname)
12
- assert reg.config[:url].include?(password)
13
-
14
- reg = Service::RegistryApi.new(:url => url)
15
- assert_equal url, reg.config[:url]
4
+ let(:url) { 'http://dockerregistry.com:5000' }
5
+ subject { Service::RegistryApi.new(url: url) }
6
+
7
+ describe '#connection' do
8
+ test 'returns a Docker::Connection' do
9
+ assert_equal Docker::Connection, subject.connection.class
10
+ end
11
+
12
+ test 'the connection has the same url' do
13
+ assert_equal url, subject.connection.url
14
+ end
15
+
16
+ context 'authentication is set' do
17
+ let(:user) { 'username' }
18
+ let(:password) { 'secretpassword' }
19
+
20
+ subject do
21
+ Service::RegistryApi.new(
22
+ :url => url,
23
+ :password => password,
24
+ :user => user
25
+ )
26
+ end
27
+
28
+ test 'it sets the same user and password' do
29
+ assert_equal user, subject.connection.options[:user]
30
+ assert_equal password, subject.connection.options[:password]
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#get' do
36
+ let(:path) { '/v1/search' }
37
+ let(:json) { '{}' }
38
+
39
+ test 'calls get on #connection' do
40
+ subject.connection
41
+ .expects(:get).at_least_once
42
+ .returns(json)
43
+
44
+ subject.get(path)
45
+ end
46
+
47
+ test 'returns a parsed json' do
48
+ subject.connection.stubs(:get).returns(json)
49
+ assert_equal JSON.parse(json), subject.get(path)
50
+ end
51
+
52
+ # Docker::Connection is used and meant for the Docker,
53
+ # not the Registry API therefore it is required
54
+ # to override the path via options
55
+ test 'sets the path as an option not param for Docker::Connection#get' do
56
+ subject.connection.stubs(:get) do |path_param, _, options|
57
+ refute_equal path, path_param
58
+ assert_equal path, options[:path]
59
+ end.returns(json)
60
+
61
+ subject.get(path)
62
+ end
63
+
64
+ # Docker Hub will return a 503 when a 'Host' header includes a port.
65
+ # Omitting default ports (an Excon option) solves the issue
66
+ test 'sets omit_default_port to true' do
67
+ subject.connection.stubs(:get) do |_, _, options|
68
+ assert options[:omit_default_port]
69
+ end.returns(json)
70
+
71
+ subject.get(path)
72
+ end
73
+
74
+ test 'returns the response raw body if it is not JSON' do
75
+ response = 'This is not JSON'
76
+ subject.connection.stubs(:get)
77
+ .returns(response)
78
+ assert_equal response, subject.get('/v1/')
79
+ end
80
+ end
81
+
82
+ describe '#search' do
83
+ let(:path) { '/v1/search' }
84
+ let(:query) { 'centos' }
85
+
86
+ test "calls #get with path and query" do
87
+ subject.expects(:get).with(path, q: query) do |path_param, params|
88
+ assert_equal path, path_param
89
+ assert_equal query, params[:q]
90
+ end.returns({})
91
+
92
+ subject.search(query)
93
+ end
94
+
95
+ test "falls back to #catalog if #get fails" do
96
+ subject.expects(:catalog).with(query)
97
+
98
+ subject.expects(:get).with(path, q: query)
99
+ .raises('Error')
100
+
101
+ subject.search(query)
102
+ end
103
+ end
104
+
105
+ describe '#catalog' do
106
+ let(:path) { '/v2/_catalog' }
107
+ let(:query) { 'centos' }
108
+ let(:catalog) { { 'repositories' => ['centos', 'fedora'] } }
109
+
110
+ setup do
111
+ subject.stubs(:get).returns(catalog)
112
+ end
113
+
114
+ test "calls #get with path" do
115
+ subject.expects(:get).with(path)
116
+ .returns(catalog)
117
+
118
+ subject.catalog(query)
119
+ end
120
+
121
+ test 'returns {"name" => value} pairs' do
122
+ result = subject.catalog(query)
123
+ assert_equal({ "name" => query }, result.first)
124
+ end
125
+
126
+ test 'only give back matching results' do
127
+ result = subject.catalog('fedora')
128
+ assert_match(/^fedora/, result.first['name'])
129
+ end
130
+ end
131
+
132
+ describe '#tags' do
133
+ let(:query) { 'alpine' }
134
+ let(:path) { "/v1/repositories/#{query}/tags" }
135
+
136
+ test "calls #get with path" do
137
+ subject.expects(:get).with(path)
138
+ subject.tags(query)
139
+ end
140
+
141
+ test "falls back to #tags_v2 if #get fails" do
142
+ subject.expects(:get).with(path)
143
+ .raises('Error')
144
+
145
+ subject.expects(:tags_v2).with(query)
146
+ subject.tags(query)
147
+ end
148
+
149
+ # https://registry.access.redhat.com returns a hash not an array
150
+ test 'handles a hash response correctly' do
151
+ tags_hash = {
152
+ "7.0-21" => "e1f5733f050b2488a17b7630cb038bfbea8b7bdfa9bdfb99e63a33117e28d02f",
153
+ "7.0-23" => "bef54b8f8a2fdd221734f1da404d4c0a7d07ee9169b1443a338ab54236c8c91a",
154
+ "7.0-27" => "8e6704f39a3d4a0c82ec7262ad683a9d1d9a281e3c1ebbb64c045b9af39b3940"
155
+ }
156
+ subject.expects(:get).with(path)
157
+ .returns(tags_hash)
158
+ assert_equal '7.0-21', subject.tags(query).first['name']
159
+ end
160
+ end
161
+
162
+ describe '#tags for API v2' do
163
+ let(:query) { 'debian' }
164
+ let(:v1_path) { "/v1/repositories/#{query}/tags" }
165
+ let(:path) { "/v2/#{query}/tags/list" }
166
+ let(:tags) { { 'tags' => ['jessy', 'woody'] } }
167
+
168
+ setup do
169
+ subject.stubs(:get).with(v1_path)
170
+ .raises('404 Not found')
171
+ end
172
+
173
+ test 'calls #get with path' do
174
+ subject.expects(:get).with(path)
175
+ .returns(tags)
176
+ subject.tags(query)
177
+ end
178
+
179
+ test 'returns {"name" => value } pairs ' do
180
+ subject.stubs(:get).with(path).returns(tags)
181
+ result = subject.tags(query)
182
+ assert_equal tags['tags'].first, result.first['name']
183
+ end
184
+ end
185
+
186
+ describe '#ok?' do
187
+ test 'calls the API via #get with /v1/' do
188
+ subject.connection.expects(:get)
189
+ .with('/', nil, Service::RegistryApi::DEFAULTS[:connection].merge(path: '/v1/'))
190
+ .returns('Docker Registry API')
191
+ assert subject.ok?
192
+ end
193
+
194
+ test 'calls #get with /v2/ if /v1/fails' do
195
+ subject.stubs(:get).with('/v1/')
196
+ .raises('404 page not found')
197
+ subject.expects(:get).with('/v2/')
198
+ .returns({})
199
+ assert subject.ok?
200
+ end
201
+ end
202
+
203
+ describe '.docker_hub' do
204
+ subject { Service::RegistryApi }
205
+
206
+ test 'returns an instance for Docker Hub' do
207
+ result = subject.docker_hub
208
+ assert_equal Service::RegistryApi::DOCKER_HUB, result.url
209
+ end
16
210
  end
17
211
  end