heroics 0.0.1
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.
- data/.gitignore +18 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +11 -0
- data/TODO +3 -0
- data/bin/heroku-api +20 -0
- data/heroics.gemspec +32 -0
- data/lib/heroics.rb +18 -0
- data/lib/heroics/cli.rb +92 -0
- data/lib/heroics/client.rb +65 -0
- data/lib/heroics/command.rb +67 -0
- data/lib/heroics/errors.rb +6 -0
- data/lib/heroics/link.rb +100 -0
- data/lib/heroics/naming.rb +19 -0
- data/lib/heroics/resource.rb +30 -0
- data/lib/heroics/schema.rb +242 -0
- data/lib/heroics/version.rb +3 -0
- data/test.rb +42 -0
- data/test/cli_test.rb +282 -0
- data/test/client_test.rb +162 -0
- data/test/command_test.rb +214 -0
- data/test/helper.rb +175 -0
- data/test/link_test.rb +349 -0
- data/test/naming_test.rb +45 -0
- data/test/resource_test.rb +35 -0
- data/test/schema_test.rb +207 -0
- data/test/version_test.rb +9 -0
- metadata +212 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Heroics
|
|
2
|
+
# Process a name to make it suitable for use as a Ruby method name.
|
|
3
|
+
#
|
|
4
|
+
# @param name [String] The name to process.
|
|
5
|
+
# @return [String] The new name with capitals converted to lowercase, and
|
|
6
|
+
# dashes and spaces converted to underscores.
|
|
7
|
+
def self.ruby_name(name)
|
|
8
|
+
name.downcase.gsub(/[- ]/, '_')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Process a name to make it suitable for use as a pretty command name.
|
|
12
|
+
#
|
|
13
|
+
# @param name [String] The name to process.
|
|
14
|
+
# @return [String] The new name with capitals converted to lowercase, and
|
|
15
|
+
# underscores and spaces converted to dashes.
|
|
16
|
+
def self.pretty_name(name)
|
|
17
|
+
name.downcase.gsub(/[_ ]/, '-')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Heroics
|
|
2
|
+
# A resource with methods mapped to API links.
|
|
3
|
+
class Resource
|
|
4
|
+
# Instantiate a resource.
|
|
5
|
+
#
|
|
6
|
+
# @param links [Hash<String,Link>] A hash that maps method names to links.
|
|
7
|
+
def initialize(links)
|
|
8
|
+
@links = links
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Find a link and invoke it.
|
|
14
|
+
#
|
|
15
|
+
# @param name [String] The name of the method to invoke.
|
|
16
|
+
# @param parameters [Array] The arguments to pass to the method. This
|
|
17
|
+
# should always be a `Hash` mapping parameter names to values.
|
|
18
|
+
# @raise [NoMethodError] Raised if the name doesn't match a known link.
|
|
19
|
+
# @return [String,Array,Hash] The response received from the server. JSON
|
|
20
|
+
# responses are automatically decoded into Ruby objects.
|
|
21
|
+
def method_missing(name, *parameters)
|
|
22
|
+
link = @links[name.to_s]
|
|
23
|
+
if link.nil?
|
|
24
|
+
address = "<#{self.class.name}:0x00#{(self.object_id << 1).to_s(16)}>"
|
|
25
|
+
raise NoMethodError.new("undefined method `#{name}' for ##{address}")
|
|
26
|
+
end
|
|
27
|
+
link.run(*parameters)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
module Heroics
|
|
2
|
+
# A wrapper around a bare JSON schema to make it easier to use.
|
|
3
|
+
class Schema
|
|
4
|
+
attr_reader :schema
|
|
5
|
+
|
|
6
|
+
# Instantiate a schema.
|
|
7
|
+
#
|
|
8
|
+
# @param schema [Hash] The bare JSON schema to wrap.
|
|
9
|
+
def initialize(schema)
|
|
10
|
+
@schema = schema
|
|
11
|
+
@resources = {}
|
|
12
|
+
@schema['definitions'].each_key do |name|
|
|
13
|
+
@resources[name] = ResourceSchema.new(@schema, name)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get a schema for a named resource.
|
|
18
|
+
#
|
|
19
|
+
# @param name [String] The name of the resource.
|
|
20
|
+
# @raise [SchemaError] Raised if an unknown resource name is provided.
|
|
21
|
+
def resource(name)
|
|
22
|
+
resource_schema = @resources[name]
|
|
23
|
+
if @schema['definitions'].has_key?(name)
|
|
24
|
+
ResourceSchema.new(@schema, name)
|
|
25
|
+
else
|
|
26
|
+
raise SchemaError.new("Unknown resource '#{name}'.")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The resource schema children that are part of this schema.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<ResourceSchema>] The resource schema children.
|
|
33
|
+
def resources
|
|
34
|
+
@resources.values
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# A wrapper around a bare resource element in a JSON schema to make it
|
|
39
|
+
# easier to use.
|
|
40
|
+
class ResourceSchema
|
|
41
|
+
attr_reader :name
|
|
42
|
+
|
|
43
|
+
# Instantiate a resource schema.
|
|
44
|
+
#
|
|
45
|
+
# @param schema [Hash] The bare JSON schema to wrap.
|
|
46
|
+
# @param name [String] The name of the resource to identify in the schema.
|
|
47
|
+
def initialize(schema, name)
|
|
48
|
+
@schema = schema
|
|
49
|
+
@name = name
|
|
50
|
+
link_schema = schema['definitions'][name]['links']
|
|
51
|
+
@links = Hash[link_schema.each_with_index.map do |link, link_index|
|
|
52
|
+
link_name = Heroics.ruby_name(link['title'])
|
|
53
|
+
[link_name, LinkSchema.new(schema, name, link_index)]
|
|
54
|
+
end]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get a schema for a named link.
|
|
58
|
+
#
|
|
59
|
+
# @param name [String] The name of the link.
|
|
60
|
+
# @raise [SchemaError] Raised if an unknown link name is provided.
|
|
61
|
+
def link(name)
|
|
62
|
+
schema = @links[name]
|
|
63
|
+
raise SchemaError.new("Unknown link '#{name}'.") unless schema
|
|
64
|
+
schema
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The link schema children that are part of this resource schema.
|
|
68
|
+
#
|
|
69
|
+
# @return [Array<LinkSchema>] The link schema children.
|
|
70
|
+
def links
|
|
71
|
+
@links.values
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A wrapper around a bare link element for a resource in a JSON schema to
|
|
76
|
+
# make it easier to use.
|
|
77
|
+
class LinkSchema
|
|
78
|
+
attr_reader :name, :resource_name, :description
|
|
79
|
+
|
|
80
|
+
# Instantiate a link schema.
|
|
81
|
+
#
|
|
82
|
+
# @param schema [Hash] The bare JSON schema to wrap.
|
|
83
|
+
# @param resource_name [String] The name of the resource to identify in
|
|
84
|
+
# the schema.
|
|
85
|
+
# @param link_index [Fixnum] The index of the link in the resource schema.
|
|
86
|
+
def initialize(schema, resource_name, link_index)
|
|
87
|
+
@schema = schema
|
|
88
|
+
@resource_name = resource_name
|
|
89
|
+
@link_index = link_index
|
|
90
|
+
@name = Heroics.ruby_name(link_schema['title'])
|
|
91
|
+
@description = link_schema['description']
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Get the resource name in pretty form.
|
|
95
|
+
#
|
|
96
|
+
# @return [String] The pretty resource name.
|
|
97
|
+
def pretty_resource_name
|
|
98
|
+
Heroics.pretty_name(resource_name)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get the link name in pretty form.
|
|
102
|
+
#
|
|
103
|
+
# @return [String] The pretty link name.
|
|
104
|
+
def pretty_name
|
|
105
|
+
Heroics.pretty_name(name)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Get the HTTP method for this link.
|
|
109
|
+
#
|
|
110
|
+
# @return [Symbol] The HTTP method.
|
|
111
|
+
def method
|
|
112
|
+
link_schema['method'].downcase.to_sym
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Get the names of the parameters this link expects.
|
|
116
|
+
#
|
|
117
|
+
# @return [Array<String>] The parameters.
|
|
118
|
+
def parameters
|
|
119
|
+
resolve_parameters(link_schema['href'].scan(PARAMETER_REGEX))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Get an example request body.
|
|
123
|
+
#
|
|
124
|
+
# @return [Hash] A sample request body.
|
|
125
|
+
def example_body
|
|
126
|
+
if body_schema = link_schema['schema']
|
|
127
|
+
definitions = @schema['definitions'][@resource_name]['definitions']
|
|
128
|
+
Hash[body_schema['properties'].keys.map do |property|
|
|
129
|
+
# FIXME This is wrong! -jkakar
|
|
130
|
+
if definitions.has_key?(property)
|
|
131
|
+
example = definitions[property]['example']
|
|
132
|
+
else
|
|
133
|
+
example = ''
|
|
134
|
+
end
|
|
135
|
+
[property, example]
|
|
136
|
+
end]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Inject parameters into the link href and return the body, if it exists.
|
|
141
|
+
#
|
|
142
|
+
# @param parameters [Array] The list of parameters to inject into the
|
|
143
|
+
# path.
|
|
144
|
+
# @raise [ArgumentError] Raised if either too many or too few parameters
|
|
145
|
+
# were provided.
|
|
146
|
+
# @return [String,Object] A path and request body pair. The body value is
|
|
147
|
+
# nil if a payload wasn't included in the list of parameters.
|
|
148
|
+
def format_path(parameters)
|
|
149
|
+
path = link_schema['href']
|
|
150
|
+
parameter_size = path.scan(PARAMETER_REGEX).size
|
|
151
|
+
too_few_parameters = parameter_size > parameters.size
|
|
152
|
+
# FIXME We should use the schema to detect when a request body is
|
|
153
|
+
# permitted and do the calculation correctly here. -jkakar
|
|
154
|
+
too_many_parameters = parameter_size < (parameters.size - 1)
|
|
155
|
+
if too_few_parameters || too_many_parameters
|
|
156
|
+
raise ArgumentError.new("wrong number of arguments " +
|
|
157
|
+
"(#{parameters.size} for #{parameter_size})")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
(0..parameter_size).each do |i|
|
|
161
|
+
path = path.sub(PARAMETER_REGEX, format_parameter(parameters[i]))
|
|
162
|
+
end
|
|
163
|
+
body = parameters.slice(parameter_size)
|
|
164
|
+
return path, body
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
# Match parameters in definition strings.
|
|
170
|
+
PARAMETER_REGEX = /\{\([%\/a-zA-Z0-9_]*\)\}/
|
|
171
|
+
|
|
172
|
+
# Get the raw link schema.
|
|
173
|
+
#
|
|
174
|
+
# @param [Hash] The raw link schema.
|
|
175
|
+
def link_schema
|
|
176
|
+
@schema['definitions'][@resource_name]['links'][@link_index]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Get the names of the parameters this link expects.
|
|
180
|
+
#
|
|
181
|
+
# @param parameters [Array] The names of the parameter definitions to
|
|
182
|
+
# convert to parameter names.
|
|
183
|
+
# @return [Array<String>] The parameters.
|
|
184
|
+
def resolve_parameters(parameters)
|
|
185
|
+
# FIXME This is all pretty terrible. It'd be much better to
|
|
186
|
+
# automatically resolve $ref's based on the path instead of special
|
|
187
|
+
# casing everything. -jkakar
|
|
188
|
+
properties = @schema['definitions'][@resource_name]['properties']
|
|
189
|
+
definitions = Hash[properties.each_pair.map do |key, value|
|
|
190
|
+
[value['$ref'], key]
|
|
191
|
+
end]
|
|
192
|
+
parameters.map do |parameter|
|
|
193
|
+
definition_name = URI.unescape(parameter[2..-3])
|
|
194
|
+
if definitions.has_key?(definition_name)
|
|
195
|
+
definitions[definition_name]
|
|
196
|
+
else
|
|
197
|
+
definition_name = definition_name.split('/')[-1]
|
|
198
|
+
resource_definitions = @schema[
|
|
199
|
+
'definitions'][@resource_name]['definitions'][definition_name]
|
|
200
|
+
if resource_definitions.has_key?('anyOf')
|
|
201
|
+
resource_definitions['anyOf'].map do |property|
|
|
202
|
+
definitions[property['$ref']]
|
|
203
|
+
end.join('|')
|
|
204
|
+
else
|
|
205
|
+
resource_definitions['oneOf'].map do |property|
|
|
206
|
+
definitions[property['$ref']]
|
|
207
|
+
end.join('|')
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Convert a path parameter to a format suitable for use in a path.
|
|
214
|
+
#
|
|
215
|
+
# @param [Fixnum,String,TrueClass,FalseClass,Time] The parameter to format.
|
|
216
|
+
# @return [String] The formatted parameter.
|
|
217
|
+
def format_parameter(parameter)
|
|
218
|
+
parameter.instance_of?(Time) ? iso_format(parameter) : parameter.to_s
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Convert a time to an ISO 8601 combined data and time format.
|
|
222
|
+
#
|
|
223
|
+
# @param time [Time] The time to convert to ISO 8601 format.
|
|
224
|
+
# @return [String] An ISO 8601 date in `YYYY-MM-DDTHH:MM:SSZ` format.
|
|
225
|
+
def iso_format(time)
|
|
226
|
+
time.getutc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Download a JSON schema from a URL.
|
|
231
|
+
#
|
|
232
|
+
# @param url [String] The URL for the schema.
|
|
233
|
+
# @param options [Hash] Configuration for links. Possible keys include:
|
|
234
|
+
# - default_headers: Optionally, a set of headers to include in every
|
|
235
|
+
# request made by the client. Default is no custom headers.
|
|
236
|
+
# @return [Schema] The downloaded JSON schema.
|
|
237
|
+
def self.download_schema(url, options={})
|
|
238
|
+
default_headers = options.fetch(:default_headers, {})
|
|
239
|
+
response = Excon.get(url, headers: default_headers, expects: [200, 201])
|
|
240
|
+
Schema.new(MultiJson.decode(response.body))
|
|
241
|
+
end
|
|
242
|
+
end
|
data/test.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'netrc'
|
|
2
|
+
|
|
3
|
+
_, token = Netrc.read['api.heroku.com']
|
|
4
|
+
|
|
5
|
+
require './lib/heroics'
|
|
6
|
+
|
|
7
|
+
heroics = Heroics.new(
|
|
8
|
+
:cache => Heroics::FileCache.new(token),
|
|
9
|
+
:token => token
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
heroics.apps.list
|
|
13
|
+
apps = heroics.apps.list # should use cache
|
|
14
|
+
puts(apps)
|
|
15
|
+
puts
|
|
16
|
+
|
|
17
|
+
app = heroics.apps.info('stringer-geemus')
|
|
18
|
+
puts(app)
|
|
19
|
+
puts
|
|
20
|
+
|
|
21
|
+
puts(heroics.apps('stringer-geemus'))
|
|
22
|
+
puts
|
|
23
|
+
|
|
24
|
+
collaborators = app.collaborators.list
|
|
25
|
+
puts(collaborators)
|
|
26
|
+
puts
|
|
27
|
+
|
|
28
|
+
collaborator_id = collaborators.first.id
|
|
29
|
+
collaborator = app.collaborators.info(collaborator_id)
|
|
30
|
+
puts(collaborator)
|
|
31
|
+
puts
|
|
32
|
+
|
|
33
|
+
regions = heroics.regions.list
|
|
34
|
+
puts(regions)
|
|
35
|
+
puts
|
|
36
|
+
|
|
37
|
+
region_id = regions.first.id
|
|
38
|
+
puts(heroics.regions.info(region_id))
|
|
39
|
+
puts
|
|
40
|
+
|
|
41
|
+
puts(heroics.regions(region_id))
|
|
42
|
+
puts
|
data/test/cli_test.rb
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
|
|
4
|
+
class CLITest < MiniTest::Unit::TestCase
|
|
5
|
+
include ExconHelper
|
|
6
|
+
|
|
7
|
+
# CLI.run displays usage information when no arguments are provided.
|
|
8
|
+
def test_run_without_arguments
|
|
9
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
10
|
+
client = Heroics::client_from_schema(schema, 'https://example.com')
|
|
11
|
+
output = StringIO.new
|
|
12
|
+
command1 = Heroics::Command.new(
|
|
13
|
+
'cli', schema.resource('resource').link('list'), client, output)
|
|
14
|
+
command2 = Heroics::Command.new(
|
|
15
|
+
'cli', schema.resource('resource').link('info'), client, output)
|
|
16
|
+
cli = Heroics::CLI.new('cli', {'resource:list' => command1,
|
|
17
|
+
'resource:info' => command2}, output)
|
|
18
|
+
cli.run
|
|
19
|
+
expected = <<-USAGE
|
|
20
|
+
Usage: cli <command> [<parameter> [...]] [<body>]
|
|
21
|
+
|
|
22
|
+
Help topics, type "cli help <topic>" for more details:
|
|
23
|
+
|
|
24
|
+
resource:info Show a sample resource
|
|
25
|
+
resource:list Show all sample resources
|
|
26
|
+
USAGE
|
|
27
|
+
assert_equal(expected, output.string)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# CLI.run displays usage information when the help command is specified.
|
|
31
|
+
def test_run_with_help_command
|
|
32
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
33
|
+
client = Heroics::client_from_schema(schema, 'https://example.com')
|
|
34
|
+
output = StringIO.new
|
|
35
|
+
command1 = Heroics::Command.new(
|
|
36
|
+
'cli', schema.resource('resource').link('list'), client, output)
|
|
37
|
+
command2 = Heroics::Command.new(
|
|
38
|
+
'cli', schema.resource('resource').link('info'), client, output)
|
|
39
|
+
cli = Heroics::CLI.new('cli', {'resource:list' => command1,
|
|
40
|
+
'resource:info' => command2}, output)
|
|
41
|
+
cli.run('help')
|
|
42
|
+
expected = <<-USAGE
|
|
43
|
+
Usage: cli <command> [<parameter> [...]] [<body>]
|
|
44
|
+
|
|
45
|
+
Help topics, type "cli help <topic>" for more details:
|
|
46
|
+
|
|
47
|
+
resource:info Show a sample resource
|
|
48
|
+
resource:list Show all sample resources
|
|
49
|
+
USAGE
|
|
50
|
+
assert_equal(expected, output.string)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# CLI.run displays command-specific help when a command name is included
|
|
54
|
+
# with the 'help' command.
|
|
55
|
+
def test_run_with_help_command_and_explicit_command_name
|
|
56
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
57
|
+
client = Heroics::client_from_schema(schema, 'https://example.com')
|
|
58
|
+
output = StringIO.new
|
|
59
|
+
command1 = Heroics::Command.new(
|
|
60
|
+
'cli', schema.resource('resource').link('list'), client, output)
|
|
61
|
+
command2 = Heroics::Command.new(
|
|
62
|
+
'cli', schema.resource('resource').link('info'), client, output)
|
|
63
|
+
cli = Heroics::CLI.new('cli', {'resource:list' => command1,
|
|
64
|
+
'resource:info' => command2}, output)
|
|
65
|
+
cli.run('help', 'resource:info')
|
|
66
|
+
expected = <<-USAGE
|
|
67
|
+
Usage: cli resource:info <uuid_field>
|
|
68
|
+
|
|
69
|
+
Description:
|
|
70
|
+
Show a sample resource
|
|
71
|
+
USAGE
|
|
72
|
+
assert_equal(expected, output.string)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# CLI.run displays an error message when no commands have been registered.
|
|
76
|
+
def test_run_without_commands
|
|
77
|
+
output = StringIO.new
|
|
78
|
+
cli = Heroics::CLI.new('cli', {}, output)
|
|
79
|
+
cli.run('help')
|
|
80
|
+
assert_equal('No commands are available.', output.string)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# CLI.run displays an error message when an unknown command name is used.
|
|
84
|
+
def test_run_with_unknown_name
|
|
85
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
86
|
+
client = Heroics::client_from_schema(schema, 'https://example.com')
|
|
87
|
+
output = StringIO.new
|
|
88
|
+
command = Heroics::Command.new(
|
|
89
|
+
'cli', schema.resource('resource').link('list'), client, output)
|
|
90
|
+
cli = Heroics::CLI.new('cli', {'resource:list' => command}, output)
|
|
91
|
+
cli.run('unknown:command')
|
|
92
|
+
assert_equal("There is no command called 'unknown:command'.\n",
|
|
93
|
+
output.string)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# CLI.run runs the command matching the specified name.
|
|
97
|
+
def test_run_with_dashed_command_name
|
|
98
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
99
|
+
client = Heroics::client_from_schema(schema, 'https://example.com')
|
|
100
|
+
output = StringIO.new
|
|
101
|
+
command = Heroics::Command.new(
|
|
102
|
+
'cli', schema.resource('resource').link('identify_resource'), client,
|
|
103
|
+
output)
|
|
104
|
+
cli = Heroics::CLI.new('cli', {'resource:identify-resource' => command},
|
|
105
|
+
output)
|
|
106
|
+
|
|
107
|
+
uuid = '1ab1c589-df46-40aa-b786-60e83b1efb10'
|
|
108
|
+
Excon.stub(method: :get) do |request|
|
|
109
|
+
assert_equal("/resource/#{uuid}", request[:path])
|
|
110
|
+
Excon.stubs.pop
|
|
111
|
+
{status: 200}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
cli.run('resource:identify-resource', uuid)
|
|
115
|
+
assert_equal('', output.string)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# CLI.run runs the resource matching the specified name.
|
|
119
|
+
def test_run_with_dashed_resource_name
|
|
120
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
121
|
+
client = Heroics::client_from_schema(schema, 'https://example.com')
|
|
122
|
+
output = StringIO.new
|
|
123
|
+
command = Heroics::Command.new(
|
|
124
|
+
'cli', schema.resource('another-resource').link('list'), client, output)
|
|
125
|
+
cli = Heroics::CLI.new('cli', {'another-resource:list' => command},
|
|
126
|
+
output)
|
|
127
|
+
|
|
128
|
+
result = {'Hello' => 'World!'}
|
|
129
|
+
Excon.stub(method: :get) do |request|
|
|
130
|
+
assert_equal("/another-resource", request[:path])
|
|
131
|
+
Excon.stubs.pop
|
|
132
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
|
133
|
+
body: MultiJson.dump(result)}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
cli.run('another-resource:list')
|
|
137
|
+
assert_equal(MultiJson.dump(result, pretty: true) + "\n", output.string)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# CLI.run runs the command matching the specified name and passes parameters
|
|
141
|
+
# to it.
|
|
142
|
+
def test_run_with_parameters
|
|
143
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
144
|
+
client = Heroics::client_from_schema(schema, 'https://example.com')
|
|
145
|
+
output = StringIO.new
|
|
146
|
+
command = Heroics::Command.new(
|
|
147
|
+
'cli', schema.resource('resource').link('update'), client, output)
|
|
148
|
+
cli = Heroics::CLI.new('cli', {'resource:update' => command}, output)
|
|
149
|
+
|
|
150
|
+
uuid = '1ab1c589-df46-40aa-b786-60e83b1efb10'
|
|
151
|
+
body = {'Hello' => 'World!'}
|
|
152
|
+
result = {'Goodbye' => 'Universe!'}
|
|
153
|
+
Excon.stub(method: :patch) do |request|
|
|
154
|
+
assert_equal("/resource/#{uuid}", request[:path])
|
|
155
|
+
assert_equal('application/json', request[:headers]['Content-Type'])
|
|
156
|
+
assert_equal(body, MultiJson.load(request[:body]))
|
|
157
|
+
Excon.stubs.pop
|
|
158
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
|
159
|
+
body: MultiJson.dump(result)}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
cli.run('resource:update', uuid, body)
|
|
163
|
+
assert_equal(MultiJson.dump(result, pretty: true) + "\n", output.string)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
class CLIFromSchemaTest < MiniTest::Unit::TestCase
|
|
168
|
+
include ExconHelper
|
|
169
|
+
|
|
170
|
+
# cli_from_schema returns a CLI generated from the specified schema.
|
|
171
|
+
def test_cli_from_schema
|
|
172
|
+
uuid = '1ab1c589-df46-40aa-b786-60e83b1efb10'
|
|
173
|
+
body = {'Hello' => 'World!'}
|
|
174
|
+
result = {'Goodbye' => 'Universe!'}
|
|
175
|
+
Excon.stub(method: :patch) do |request|
|
|
176
|
+
assert_equal("/resource/#{uuid}", request[:path])
|
|
177
|
+
assert_equal('application/json', request[:headers]['Content-Type'])
|
|
178
|
+
assert_equal(body, MultiJson.load(request[:body]))
|
|
179
|
+
Excon.stubs.pop
|
|
180
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
|
181
|
+
body: MultiJson.dump(result)}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
185
|
+
output = StringIO.new
|
|
186
|
+
cli = Heroics.cli_from_schema('cli', output, schema, 'https://example.com')
|
|
187
|
+
cli.run('resource:update', uuid, body)
|
|
188
|
+
assert_equal(MultiJson.dump(result, pretty: true) + "\n", output.string)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# cli_from_schema optionally accepts custom headers to pass with every
|
|
192
|
+
# request made by the generated CLI.
|
|
193
|
+
def test_cli_from_schema_with_custom_headers
|
|
194
|
+
uuid = '1ab1c589-df46-40aa-b786-60e83b1efb10'
|
|
195
|
+
body = {'Hello' => 'World!'}
|
|
196
|
+
result = {'Goodbye' => 'Universe!'}
|
|
197
|
+
Excon.stub(method: :patch) do |request|
|
|
198
|
+
assert_equal('application/vnd.heroku+json; version=3',
|
|
199
|
+
request[:headers]['Accept'])
|
|
200
|
+
Excon.stubs.pop
|
|
201
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
|
202
|
+
body: MultiJson.dump(result)}
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
schema = Heroics::Schema.new(SAMPLE_SCHEMA)
|
|
206
|
+
output = StringIO.new
|
|
207
|
+
cli = Heroics.cli_from_schema(
|
|
208
|
+
'cli', output, schema, 'https://example.com',
|
|
209
|
+
default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'})
|
|
210
|
+
cli.run('resource:update', uuid, body)
|
|
211
|
+
assert_equal(MultiJson.dump(result, pretty: true) + "\n", output.string)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
class CLIFromSchemaURLTest < MiniTest::Unit::TestCase
|
|
216
|
+
include ExconHelper
|
|
217
|
+
|
|
218
|
+
# client_from_schema_url downloads a schema and returns a Client generated
|
|
219
|
+
# from it.
|
|
220
|
+
def test_cli_from_schema_url
|
|
221
|
+
Excon.stub(method: :get) do |request|
|
|
222
|
+
assert_equal('example.com', request[:host])
|
|
223
|
+
assert_equal('/schema', request[:path])
|
|
224
|
+
Excon.stubs.pop
|
|
225
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
|
226
|
+
body: MultiJson.dump(SAMPLE_SCHEMA)}
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
output = StringIO.new
|
|
230
|
+
cli = Heroics.cli_from_schema_url('cli', output,
|
|
231
|
+
'https://example.com/schema')
|
|
232
|
+
|
|
233
|
+
uuid = '1ab1c589-df46-40aa-b786-60e83b1efb10'
|
|
234
|
+
body = {'Hello' => 'World!'}
|
|
235
|
+
result = {'Goodbye' => 'Universe!'}
|
|
236
|
+
Excon.stub(method: :patch) do |request|
|
|
237
|
+
assert_equal("/resource/#{uuid}", request[:path])
|
|
238
|
+
assert_equal('application/json', request[:headers]['Content-Type'])
|
|
239
|
+
assert_equal(body, MultiJson.load(request[:body]))
|
|
240
|
+
Excon.stubs.pop
|
|
241
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
|
242
|
+
body: MultiJson.dump(result)}
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
cli.run('resource:update', uuid, body)
|
|
246
|
+
assert_equal(MultiJson.dump(result, pretty: true) + "\n", output.string)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# cli_from_schema_url optionally accepts custom headers to include in the
|
|
250
|
+
# request to download the schema. The same headers are passed in requests
|
|
251
|
+
# made by the generated CLI.
|
|
252
|
+
def test_cli_from_schema_url_with_custom_headers
|
|
253
|
+
Excon.stub(method: :get) do |request|
|
|
254
|
+
assert_equal('example.com', request[:host])
|
|
255
|
+
assert_equal('/schema', request[:path])
|
|
256
|
+
assert_equal('application/vnd.heroku+json; version=3',
|
|
257
|
+
request[:headers]['Accept'])
|
|
258
|
+
Excon.stubs.pop
|
|
259
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
|
260
|
+
body: MultiJson.dump(SAMPLE_SCHEMA)}
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
output = StringIO.new
|
|
264
|
+
cli = Heroics.cli_from_schema_url(
|
|
265
|
+
'cli', output, 'https://example.com/schema',
|
|
266
|
+
default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'})
|
|
267
|
+
|
|
268
|
+
uuid = '1ab1c589-df46-40aa-b786-60e83b1efb10'
|
|
269
|
+
body = {'Hello' => 'World!'}
|
|
270
|
+
result = {'Goodbye' => 'Universe!'}
|
|
271
|
+
Excon.stub(method: :patch) do |request|
|
|
272
|
+
assert_equal('application/vnd.heroku+json; version=3',
|
|
273
|
+
request[:headers]['Accept'])
|
|
274
|
+
Excon.stubs.pop
|
|
275
|
+
{status: 200, headers: {'Content-Type' => 'application/json'},
|
|
276
|
+
body: MultiJson.dump(result)}
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
cli.run('resource:update', uuid, body)
|
|
280
|
+
assert_equal(MultiJson.dump(result, pretty: true) + "\n", output.string)
|
|
281
|
+
end
|
|
282
|
+
end
|