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.
@@ -0,0 +1,162 @@
1
+ require 'helper'
2
+
3
+ class ClientTest < MiniTest::Unit::TestCase
4
+ include ExconHelper
5
+
6
+ # Client.<resource> raises a NoMethodError when a method is invoked
7
+ # without a matching resource.
8
+ def test_invalid_resource
9
+ client = Heroics::Client.new({})
10
+ error = assert_raises NoMethodError do
11
+ client.unknown
12
+ end
13
+ assert_match(
14
+ /undefined method `unknown' for #<Heroics::Client:0x[0-9a-f]{14}>/,
15
+ error.message)
16
+ end
17
+
18
+ # Client.<resource>.<link> finds the appropriate link and invokes it.
19
+ def test_resource
20
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
21
+ link = Heroics::Link.new('https://username:secret@example.com',
22
+ schema.resource('resource').link('list'))
23
+ resource = Heroics::Resource.new({'link' => link})
24
+ client = Heroics::Client.new({'resource' => resource})
25
+ Excon.stub(method: :get) do |request|
26
+ assert_equal('Basic dXNlcm5hbWU6c2VjcmV0',
27
+ request[:headers]['Authorization'])
28
+ assert_equal('example.com', request[:host])
29
+ assert_equal(443, request[:port])
30
+ assert_equal('/resource', request[:path])
31
+ Excon.stubs.pop
32
+ {status: 200, body: 'Hello, world!'}
33
+ end
34
+ assert_equal('Hello, world!', client.resource.link)
35
+ end
36
+ end
37
+
38
+ class ClientFromSchemaTest < MiniTest::Unit::TestCase
39
+ include ExconHelper
40
+
41
+ # client_from_schema returns a Client generated from the specified schema.
42
+ def test_client_from_schema
43
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
44
+ client = Heroics::client_from_schema(schema, 'https://example.com')
45
+ body = {'Hello' => 'World!'}
46
+ Excon.stub(method: :post) do |request|
47
+ assert_equal('/resource', request[:path])
48
+ Excon.stubs.pop
49
+ {status: 200, headers: {'Content-Type' => 'application/json'},
50
+ body: MultiJson.dump(body)}
51
+ end
52
+ assert_equal(body, client.resource.create)
53
+ end
54
+
55
+ # client_from_schema optionally accepts custom headers to pass with every
56
+ # request made by the generated client.
57
+ def test_client_from_schema_with_custom_headers
58
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
59
+ client = Heroics::client_from_schema(
60
+ schema, 'https://example.com',
61
+ default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'})
62
+ Excon.stub(method: :post) do |request|
63
+ assert_equal('application/vnd.heroku+json; version=3',
64
+ request[:headers]['Accept'])
65
+ Excon.stubs.pop
66
+ {status: 200}
67
+ end
68
+ client.resource.create
69
+ end
70
+
71
+ # client_from_schema takes an optional :cache parameter which it uses when
72
+ # constructing Link instances.
73
+ def test_client_from_schema_with_cache
74
+ body = {'Hello' => 'World!'}
75
+ Excon.stub(method: :get) do |request|
76
+ Excon.stubs.pop
77
+ {status: 201, headers: {'Content-Type' => 'application/json',
78
+ 'ETag' => 'etag-contents'},
79
+ body: MultiJson.dump(body)}
80
+ end
81
+
82
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
83
+ client = Heroics::client_from_schema(schema, 'https://example.com',
84
+ cache: Moneta.new(:Memory))
85
+ assert_equal(body, client.resource.list)
86
+
87
+ Excon.stub(method: :get) do |request|
88
+ assert_equal('etag-contents', request[:headers]['If-None-Match'])
89
+ Excon.stubs.pop
90
+ {status: 304, headers: {'Content-Type' => 'application/json'}}
91
+ end
92
+ assert_equal(body, client.resource.list)
93
+ end
94
+ end
95
+
96
+ class ClientFromSchemaURLTest < MiniTest::Unit::TestCase
97
+ include ExconHelper
98
+
99
+ # client_from_schema_url downloads a schema and returns a Client generated
100
+ # from it.
101
+ def test_client_from_schema_url
102
+ Excon.stub(method: :get) do |request|
103
+ assert_equal('example.com', request[:host])
104
+ assert_equal('/schema', request[:path])
105
+ Excon.stubs.pop
106
+ {status: 200, headers: {'Content-Type' => 'application/json'},
107
+ body: MultiJson.dump(SAMPLE_SCHEMA)}
108
+ end
109
+
110
+ client = Heroics::client_from_schema_url('https://example.com/schema')
111
+ body = {'Hello' => 'World!'}
112
+ Excon.stub(method: :post) do |request|
113
+ assert_equal('example.com', request[:host])
114
+ assert_equal('/resource', request[:path])
115
+ Excon.stubs.pop
116
+ {status: 200, headers: {'Content-Type' => 'application/json'},
117
+ body: MultiJson.dump(body)}
118
+ end
119
+ assert_equal(body, client.resource.create)
120
+ end
121
+
122
+ # client_from_schema_url optionally accepts custom headers to include in the
123
+ # request to download the schema. The same headers are passed in requests
124
+ # made by the generated client.
125
+ def test_client_from_schema_url_with_custom_headers
126
+ Excon.stub(method: :get) do |request|
127
+ assert_equal('example.com', request[:host])
128
+ assert_equal('/schema', request[:path])
129
+ assert_equal('application/vnd.heroku+json; version=3',
130
+ request[:headers]['Accept'])
131
+ Excon.stubs.pop
132
+ {status: 200, headers: {'Content-Type' => 'application/json'},
133
+ body: MultiJson.dump(SAMPLE_SCHEMA)}
134
+ end
135
+
136
+ client = Heroics::client_from_schema_url(
137
+ 'https://example.com/schema',
138
+ default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'})
139
+ body = {'Hello' => 'World!'}
140
+ Excon.stub(method: :post) do |request|
141
+ assert_equal('application/vnd.heroku+json; version=3',
142
+ request[:headers]['Accept'])
143
+ Excon.stubs.pop
144
+ {status: 200, headers: {'Content-Type' => 'application/json'},
145
+ body: MultiJson.dump(body)}
146
+ end
147
+ assert_equal(body, client.resource.create)
148
+ end
149
+
150
+ # client_from_schema_url raises an Excon error when the request to download
151
+ # the schema fails.
152
+ def test_client_from_schema_url_with_failed_request
153
+ Excon.stub(method: :get) do |request|
154
+ Excon.stubs.pop
155
+ {status: 404}
156
+ end
157
+
158
+ assert_raises Excon::Errors::NotFound do
159
+ Heroics::client_from_schema_url('https://example.com/schema')
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,214 @@
1
+ require 'helper'
2
+ require 'stringio'
3
+
4
+ class CommandTest < MiniTest::Unit::TestCase
5
+ include ExconHelper
6
+
7
+ # Command.name returns the name of the command, which is made up by joining
8
+ # the resource name and link title with a colon.
9
+ def test_name
10
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
11
+ client = Heroics::client_from_schema(schema, 'https://example.com')
12
+ output = StringIO.new
13
+ command = Heroics::Command.new(
14
+ 'cli', schema.resource('resource').link('list'), client, output)
15
+ assert_equal('resource:list', command.name)
16
+ end
17
+
18
+ # Command.description returns a description for the command.
19
+ def test_description
20
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
21
+ client = Heroics::client_from_schema(schema, 'https://example.com')
22
+ output = StringIO.new
23
+ command = Heroics::Command.new(
24
+ 'cli', schema.resource('resource').link('list'), client, output)
25
+ assert_equal('Show all sample resources', command.description)
26
+ end
27
+
28
+ # Command.run calls the correct method on the client when no link parameters
29
+ # are provided.
30
+ def test_run_without_parameters
31
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
32
+ client = Heroics::client_from_schema(schema, 'https://example.com')
33
+ output = StringIO.new
34
+ command = Heroics::Command.new(
35
+ 'cli', schema.resource('resource').link('list'), client, output)
36
+
37
+ body = ['Hello', 'World!']
38
+ Excon.stub(method: :get) do |request|
39
+ assert_equal('/resource', request[:path])
40
+ Excon.stubs.pop
41
+ {status: 200, headers: {'Content-Type' => 'application/json'},
42
+ body: MultiJson.dump(body)}
43
+ end
44
+
45
+ command.run
46
+ assert_equal(MultiJson.dump(body, pretty: true) + "\n", output.string)
47
+ end
48
+
49
+ # Command.run calls the correct method on the client and passes link
50
+ # parameters when they're provided.
51
+ def test_run_with_parameters
52
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
53
+ client = Heroics::client_from_schema(schema, 'https://example.com')
54
+ output = StringIO.new
55
+ command = Heroics::Command.new(
56
+ 'cli', schema.resource('resource').link('info'), client, output)
57
+
58
+ uuid = '1ab1c589-df46-40aa-b786-60e83b1efb10'
59
+ body = {'Hello' => 'World!'}
60
+ Excon.stub(method: :get) do |request|
61
+ assert_equal("/resource/#{uuid}", request[:path])
62
+ Excon.stubs.pop
63
+ {status: 200, headers: {'Content-Type' => 'application/json'},
64
+ body: MultiJson.dump(body)}
65
+ end
66
+
67
+ command.run(uuid)
68
+ assert_equal(MultiJson.dump(body, pretty: true) + "\n", output.string)
69
+ end
70
+
71
+ # Command.run calls the correct method on the client and passes a request
72
+ # body to the link when it's provided.
73
+ def test_run_with_request_body_and_text_response
74
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
75
+ client = Heroics::client_from_schema(schema, 'https://example.com')
76
+ output = StringIO.new
77
+ command = Heroics::Command.new(
78
+ 'cli', schema.resource('resource').link('create'), client, output)
79
+
80
+ body = {'Hello' => 'World!'}
81
+ Excon.stub(method: :post) do |request|
82
+ assert_equal('/resource', request[:path])
83
+ assert_equal('application/json', request[:headers]['Content-Type'])
84
+ assert_equal(body, MultiJson.load(request[:body]))
85
+ Excon.stubs.pop
86
+ {status: 201}
87
+ end
88
+
89
+ command.run(body)
90
+ assert_equal('', output.string)
91
+ end
92
+
93
+ # Command.run calls the correct method on the client and converts the result
94
+ # to an array, if a range response is received, before writing it out.
95
+ def test_run_with_range_response
96
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
97
+ client = Heroics::client_from_schema(schema, 'https://example.com')
98
+ output = StringIO.new
99
+ command = Heroics::Command.new(
100
+ 'cli', schema.resource('resource').link('list'), client, output)
101
+
102
+ Excon.stub(method: :get) do |request|
103
+ Excon.stubs.shift
104
+ {status: 206, headers: {'Content-Type' => 'application/json',
105
+ 'Content-Range' => 'id 1..2; max=200'},
106
+ body: MultiJson.dump([2])}
107
+ end
108
+
109
+ Excon.stub(method: :get) do |request|
110
+ Excon.stubs.shift
111
+ {status: 206, headers: {'Content-Type' => 'application/json',
112
+ 'Content-Range' => 'id 0..1; max=200',
113
+ 'Next-Range' => '201'},
114
+ body: MultiJson.dump([1])}
115
+ end
116
+
117
+ command.run
118
+ assert_equal(MultiJson.dump([1, 2], pretty: true) + "\n", output.string)
119
+ end
120
+
121
+ # Command.run calls the correct method on the client and passes parameters
122
+ # and a request body to the link when they're provided.
123
+ def test_run_with_request_body_and_parameters
124
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
125
+ client = Heroics::client_from_schema(schema, 'https://example.com')
126
+ output = StringIO.new
127
+ command = Heroics::Command.new(
128
+ 'cli', schema.resource('resource').link('update'), client, output)
129
+
130
+ uuid = '1ab1c589-df46-40aa-b786-60e83b1efb10'
131
+ body = {'Hello' => 'World!'}
132
+ result = {'Goodbye' => 'Universe!'}
133
+ Excon.stub(method: :patch) do |request|
134
+ assert_equal("/resource/#{uuid}", request[:path])
135
+ assert_equal('application/json', request[:headers]['Content-Type'])
136
+ assert_equal(body, MultiJson.load(request[:body]))
137
+ Excon.stubs.pop
138
+ {status: 200, headers: {'Content-Type' => 'application/json'},
139
+ body: MultiJson.dump(result)}
140
+ end
141
+
142
+ command.run(uuid, body)
143
+ assert_equal(MultiJson.dump(result, pretty: true) + "\n", output.string)
144
+ end
145
+
146
+ # Command.run raises an ArgumentError if too few parameters are provided.
147
+ def test_run_with_too_few_parameters
148
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
149
+ client = Heroics::client_from_schema(schema, 'https://example.com')
150
+ output = StringIO.new
151
+ command = Heroics::Command.new(
152
+ 'cli', schema.resource('resource').link('info'), client, output)
153
+ assert_raises ArgumentError do
154
+ command.run
155
+ end
156
+ end
157
+
158
+ # Command.run raises an ArgumentError if too many parameters are provided.
159
+ def test_run_with_too_many_parameters
160
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
161
+ client = Heroics::client_from_schema(schema, 'https://example.com')
162
+ output = StringIO.new
163
+ command = Heroics::Command.new(
164
+ 'cli', schema.resource('resource').link('info'), client, output)
165
+ assert_raises ArgumentError do
166
+ command.run('too', 'many', 'parameters')
167
+ end
168
+ end
169
+
170
+ # Command.usage displays usage information.
171
+ def test_usage
172
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
173
+ client = Heroics::client_from_schema(schema, 'https://example.com')
174
+ output = StringIO.new
175
+ command = Heroics::Command.new(
176
+ 'cli', schema.resource('resource').link('update'), client, output)
177
+ command.usage
178
+ expected = <<-USAGE
179
+ Usage: cli resource:update <uuid_field> <body>
180
+
181
+ Description:
182
+ Update a sample resource
183
+
184
+ Body example:
185
+ {
186
+ "date_field": "2013-10-19 22:10:29Z",
187
+ "string_field": "Sample text.",
188
+ "boolean_field": true,
189
+ "uuid_field": "44724831-bf66-4bc2-865f-e2c4c2b14c78",
190
+ "email_field": "username@example.com"
191
+ }
192
+ USAGE
193
+ assert_equal(expected, output.string)
194
+ end
195
+
196
+ # Command.usage correctly handles parameters that are described by 'oneOf'
197
+ # and 'anyOf' sub-parameter lists.
198
+ def test_usage_with_one_of_field
199
+ schema = Heroics::Schema.new(SAMPLE_SCHEMA)
200
+ client = Heroics::client_from_schema(schema, 'https://example.com')
201
+ output = StringIO.new
202
+ command = Heroics::Command.new(
203
+ 'cli', schema.resource('resource').link('identify_resource'), client,
204
+ output)
205
+ command.usage
206
+ expected = <<-USAGE
207
+ Usage: cli resource:identify-resource <uuid_field|email_field>
208
+
209
+ Description:
210
+ Show a sample resource
211
+ USAGE
212
+ assert_equal(expected, output.string)
213
+ end
214
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,175 @@
1
+ require 'minitest/autorun'
2
+ require 'time'
3
+
4
+ require 'heroics'
5
+
6
+ module ExconHelper
7
+ def setup
8
+ super
9
+ Excon.stubs.clear
10
+ Excon.defaults[:mock] = true
11
+ end
12
+
13
+ def teardown
14
+ # FIXME This is a bit ugly, but Excon doesn't provide a builtin way to
15
+ # ensure that a request was invoked, so we have to do it ourselves.
16
+ # Without this, and the Excon.stubs.pop calls in the tests that use this
17
+ # helper, tests will pass if request logic is completely removed from
18
+ # application code. -jkakar
19
+ assert(Excon.stubs.empty?, 'Expected HTTP requests were not made.')
20
+ super
21
+ end
22
+ end
23
+
24
+ # A simple JSON schema for testing purposes.
25
+ SAMPLE_SCHEMA = {
26
+ 'definitions' => {
27
+ 'resource' => {
28
+ 'description' => 'A sample resource to use in tests.',
29
+ 'id' => 'schema/resource',
30
+ '$schema' => 'http://json-schema.org/draft-04/hyper-schema',
31
+ 'title' => 'Sample resource title',
32
+ 'type' => ['object'],
33
+
34
+ 'definitions' => {
35
+ 'date_field' => {
36
+ 'description' => 'A sample date field',
37
+ 'example' => '2013-10-19 22:10:29Z',
38
+ 'format' => 'date-time',
39
+ 'readOnly' => true,
40
+ 'type' => ['string']
41
+ },
42
+
43
+ 'string_field' => {
44
+ 'description' => 'A sample string field',
45
+ 'example' => 'Sample text.',
46
+ 'readOnly' => true,
47
+ 'type' => ['string']
48
+ },
49
+
50
+ 'boolean_field' => {
51
+ 'description' => 'A sample boolean field',
52
+ 'example' => true,
53
+ 'type' => ['boolean']
54
+ },
55
+
56
+ 'uuid_field' => {
57
+ 'description' => 'A sample UUID field',
58
+ 'example' => '44724831-bf66-4bc2-865f-e2c4c2b14c78',
59
+ 'format' => 'uuid',
60
+ 'readOnly' => true,
61
+ 'type' => ['string']
62
+ },
63
+
64
+ 'email_field' => {
65
+ 'description' => 'A sample email address field',
66
+ 'example' => 'username@example.com',
67
+ 'format' => 'email',
68
+ 'readOnly' => true,
69
+ 'type' => ['string']
70
+ },
71
+
72
+ 'identity' => {
73
+ 'oneOf' => [
74
+ {'$ref' => '#/definitions/resource/definitions/uuid_field'},
75
+ {'$ref' => '#/definitions/resource/definitions/email_field'}]
76
+ }
77
+ },
78
+
79
+ 'properties' => {
80
+ 'date_field' => {
81
+ '$ref' => '#/definitions/resource/definitions/date_field'},
82
+ 'string_field' => {
83
+ '$ref' => '#/definitions/resource/definitions/string_field'},
84
+ 'boolean_field' => {
85
+ '$ref' => '#/definitions/resource/definitions/boolean_field'},
86
+ 'uuid_field' => {
87
+ '$ref' => '#/definitions/resource/definitions/uuid_field'},
88
+ 'email_field' => {
89
+ '$ref' => '#/definitions/resource/definitions/email_field'},
90
+ },
91
+
92
+ 'links' => [
93
+ {'description' => 'Show all sample resources',
94
+ 'href' => '/resource',
95
+ 'method' => 'GET',
96
+ 'rel' => 'instances',
97
+ 'title' => 'List'},
98
+
99
+ {'description' => 'Show a sample resource',
100
+ 'href' => '/resource/{(%23%2Fdefinitions%2Fresource%2Fdefinitions%2Fuuid_field)}',
101
+ 'method' => 'GET',
102
+ 'rel' => 'self',
103
+ 'title' => 'Info'},
104
+
105
+ {'description' => 'Show a sample resource',
106
+ 'href' => '/resource/{(%23%2Fdefinitions%2Fresource%2Fdefinitions%2Fidentity)}',
107
+ 'method' => 'GET',
108
+ 'rel' => 'self',
109
+ 'title' => 'Identify resource'},
110
+
111
+ {'description' => 'Create a sample resource',
112
+ 'href' => '/resource',
113
+ 'method' => 'POST',
114
+ 'rel' => 'create',
115
+ 'title' => 'Create',
116
+ 'schema' => {
117
+ 'properties' => {
118
+ 'date_field' => {
119
+ '$ref' => '#/definitions/resource/definitions/date_field'},
120
+ 'string_field' => {
121
+ '$ref' => '#/definitions/resource/definitions/string_field'},
122
+ 'boolean_field' => {
123
+ '$ref' => '#/definitions/resource/definitions/boolean_field'},
124
+ 'uuid_field' => {
125
+ '$ref' => '#/definitions/resource/definitions/uuid_field'},
126
+ 'email_field' => {
127
+ '$ref' => '#/definitions/resource/definitions/email_field'}}}},
128
+
129
+ {'description' => 'Update a sample resource',
130
+ 'href' => '/resource/{(%23%2Fdefinitions%2Fresource%2Fdefinitions%2Fuuid_field)}',
131
+ 'method' => 'PATCH',
132
+ 'rel' => 'update',
133
+ 'title' => 'Update',
134
+ 'schema' => {
135
+ 'properties' => {
136
+ 'date_field' => {
137
+ '$ref' => '#/definitions/resource/definitions/date_field'},
138
+ 'string_field' => {
139
+ '$ref' => '#/definitions/resource/definitions/string_field'},
140
+ 'boolean_field' => {
141
+ '$ref' => '#/definitions/resource/definitions/boolean_field'},
142
+ 'uuid_field' => {
143
+ '$ref' => '#/definitions/resource/definitions/uuid_field'},
144
+ 'email_field' => {
145
+ '$ref' => '#/definitions/resource/definitions/email_field'}}}},
146
+
147
+ {'description' => 'Delete an existing sample resource at specific time',
148
+ 'href' => '/resource/{(%23%2Fdefinitions%2Fresource%2Fdefinitions%2Fdate_field)}',
149
+ 'method' => 'DELETE',
150
+ 'rel' => 'destroy',
151
+ 'title' => 'Delete'}
152
+ ]
153
+ },
154
+
155
+ 'another-resource' => {
156
+ 'description' => 'Another sample resource to use in tests.',
157
+ 'id' => 'schema/another-resource',
158
+ '$schema' => 'http://json-schema.org/draft-04/hyper-schema',
159
+ 'title' => 'Another sample resource title',
160
+ 'type' => ['object'],
161
+
162
+ 'definitions' => {},
163
+
164
+ 'properties' => {},
165
+
166
+ 'links' => [
167
+ {'description' => 'Show all sample resources',
168
+ 'href' => '/another-resource',
169
+ 'method' => 'GET',
170
+ 'rel' => 'instances',
171
+ 'title' => 'List'}
172
+ ]
173
+ },
174
+ }
175
+ }