frenchy 0.0.9 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ class MyModel
4
+ include Frenchy::Model
5
+ end
6
+
7
+ class MyModelDecorator
8
+ def self.decorate_collection(collection, options={})
9
+ return "DECORATED"
10
+ end
11
+ end
12
+
13
+ describe Frenchy::Collection do
14
+ describe "#decorate" do
15
+ describe "when there are no items" do
16
+ it "returns an empty array" do
17
+ coll = Frenchy::Collection.new
18
+ expect(coll.decorate).to eql([])
19
+ end
20
+ end
21
+
22
+ describe "when there are model items" do
23
+ it "decorates using the named convention" do
24
+ m1 = MyModel.new
25
+ m2 = MyModel.new
26
+ coll = Frenchy::Collection.new([m1, m2])
27
+ expect(coll.decorate).to eql("DECORATED")
28
+ end
29
+
30
+ it "supports a hash of options" do
31
+ m1 = MyModel.new
32
+ m2 = MyModel.new
33
+ coll = Frenchy::Collection.new([m1, m2])
34
+ expect(coll.decorate({"a" => 1})).to eq("DECORATED") # test arity
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ class MyOtherClass
4
+ include Frenchy::Model
5
+ end
6
+
7
+ class MyClass
8
+ include Frenchy::Model
9
+
10
+ field :other, type: "my_other_class"
11
+ end
12
+
13
+ describe Hash do
14
+ describe "#stringify_keys" do
15
+ it "converts symbol keyed has to string keyed" do
16
+ expect({a: 1, b: 2}.stringify_keys!).to eql({"a" => 1, "b" => 2})
17
+ end
18
+ end
19
+ end
20
+
21
+ describe String do
22
+ describe "#constantize" do
23
+ it "properly constantizes a string" do
24
+ expect("MyClass".constantize).to eql(MyClass)
25
+ end
26
+ end
27
+
28
+ describe "#camelize" do
29
+ it "converts under_score to CamelCase" do
30
+ expect("my_class".camelize).to eql("MyClass")
31
+ expect("just_a_model".camelize).to eql("JustAModel")
32
+ end
33
+ end
34
+
35
+ describe "#underscore" do
36
+ it "converts CamelCase to under_score" do
37
+ expect("MyClass".underscore).to eql("my_class")
38
+ expect("JustAModel".underscore).to eql("just_a_model")
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,69 @@
1
+ require "spec_helper"
2
+
3
+ describe Frenchy::RequestError do
4
+ describe "#message" do
5
+ describe "with an exception" do
6
+ it "uses the message from the exception" do
7
+ ex = EOFError.new("reached eof")
8
+ error = Frenchy::RequestError.new(ex.message)
9
+
10
+ message = begin
11
+ raise(error, ex)
12
+ rescue => e
13
+ e.message
14
+ end
15
+
16
+ expect(message).to eql("reached eof")
17
+ end
18
+
19
+ it "can be raised" do
20
+ ex = EOFError.new("reached eof")
21
+ error = Frenchy::RequestError
22
+ expect do
23
+ raise(error, ex)
24
+ end.to raise_error(Frenchy::RequestError, "reached eof")
25
+ end
26
+ end
27
+
28
+ describe "with a response" do
29
+ it "uses the status of the response" do
30
+ response = instance_double("Net::HTTPResponse", code: "500", body: "internal server error")
31
+ error = Frenchy::RequestError.new(nil, nil, response)
32
+
33
+ message = begin
34
+ raise(error)
35
+ rescue => e
36
+ e.message
37
+ end
38
+
39
+ expect(message).to include("500")
40
+ expect(message).to include("internal server error")
41
+ end
42
+
43
+ it "can be raised" do
44
+ response = instance_double("Net::HTTPResponse", code: "500", body: "internal server error")
45
+ error = Frenchy::RequestError.new(nil, response)
46
+ expect do
47
+ raise error, "something"
48
+ end.to raise_error(Frenchy::RequestError)
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#request" do
54
+ it "contains the original request" do
55
+ request = "GET https://api.github.com"
56
+ error = Frenchy::RequestError.new(nil, request, nil)
57
+ expect(error.request).to eql(request)
58
+ end
59
+ end
60
+
61
+ describe "#response" do
62
+ it "contains the original response" do
63
+ response = instance_double("Net::HTTPResponse", code: "500", body: "internal server error")
64
+ error = Frenchy::RequestError.new(nil, nil, response)
65
+ expect(error.response).to eql(response)
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,213 @@
1
+ require "spec_helper"
2
+
3
+ class SimpleModel
4
+ include Frenchy::Model
5
+
6
+ field :id, type: "integer"
7
+ field :name, type: "string", default: "unknown"
8
+ field :occupation, type: "string"
9
+ end
10
+
11
+ class SpecialItem
12
+ include Frenchy::Model
13
+
14
+ field :id, type: "integer"
15
+ end
16
+
17
+ class SuperSpecialItem
18
+ include Frenchy::Model
19
+
20
+ field :id, type: "integer"
21
+ end
22
+
23
+ class Box
24
+ include Frenchy::Model
25
+
26
+ key :name
27
+
28
+ field :id, type: "integer"
29
+ field :name, type: "string"
30
+ field :gpa, type: "float", aliases: [:grade, :grade_point_average]
31
+ field :happy, type: "bool"
32
+ field :birth, type: "time"
33
+ field :aliases, type: "array"
34
+ field :extras, type: "hash"
35
+
36
+ field :item, type: "special_item"
37
+ field :items, type: "special_item", many: true
38
+ field :special, type: "special_item", class_name: "SuperSpecialItem"
39
+ end
40
+
41
+ class SimpleModelDecorator
42
+ def self.decorate(object, options={})
43
+ "DECORATED"
44
+ end
45
+ end
46
+
47
+ describe Frenchy::Model do
48
+ describe "#initialize" do
49
+ it "assigns given attributes" do
50
+ model = SimpleModel.new(id: 1, name: "bob")
51
+ expect(model.id).to eq(1)
52
+ expect(model.name).to eq("bob")
53
+ end
54
+
55
+ it "assigns defaults when attributes are missing" do
56
+ model = SimpleModel.new(id: 1)
57
+ expect(model.name).to eq("unknown")
58
+ end
59
+ end
60
+
61
+ describe "#attributes" do
62
+ it "includes all attributes, present or not" do
63
+ model = SimpleModel.new(id: 1)
64
+ expect(model.attributes).to eq({"id" => 1, "name" => "unknown", "occupation" => nil})
65
+ end
66
+ end
67
+
68
+ describe "#decorate" do
69
+ it "decorates the model using the named convention" do
70
+ model = SimpleModel.new
71
+ expect(model.decorate).to eq("DECORATED")
72
+ end
73
+
74
+ it "supports a hash of options" do
75
+ model = SimpleModel.new
76
+ expect(model.decorate({"a" => 1})).to eq("DECORATED") # test arity
77
+ end
78
+ end
79
+
80
+ describe ".key" do
81
+ it "provides to_param method" do
82
+ m = Box.new(id: 5, name: "john")
83
+ expect(m.to_param).to eql("john")
84
+ end
85
+ end
86
+
87
+ describe ".field" do
88
+ describe "aliases" do
89
+ it "aliases fields" do
90
+ m = Box.new(gpa: 5.0)
91
+ expect(m.gpa).to eql(5.0)
92
+ expect(m.grade).to eql(5.0)
93
+ expect(m.grade_point_average).to eql(5.0)
94
+ end
95
+ end
96
+
97
+ describe "string" do
98
+ it "converts values to a String" do
99
+ expect(Box.new(name: 1234).name).to eql("1234")
100
+ end
101
+ end
102
+
103
+ describe "integer" do
104
+ it "converts values to an Integer" do
105
+ expect(Box.new(id: "1234").id).to eql(1234)
106
+ end
107
+
108
+ it "raises an error with invalid values" do
109
+ expect{Box.new(id: "a")}.to raise_error(ArgumentError)
110
+ end
111
+ end
112
+
113
+ describe "float" do
114
+ it "converts values to a Float" do
115
+ expect(Box.new(gpa: "1").gpa).to eql(1.0)
116
+ end
117
+
118
+ it "raises an error with invalid values" do
119
+ expect{Box.new(gpa: "a")}.to raise_error(ArgumentError)
120
+ end
121
+ end
122
+
123
+ describe "bool" do
124
+ it "converts truthy values to true" do
125
+ ["true", "1", 1, true].each do |v|
126
+ expect(Box.new(happy: v).happy).to eql(true)
127
+ end
128
+ end
129
+
130
+ it "converts non-truthy values to false" do
131
+ ["false", "0", 0, false].each do |v|
132
+ expect(Box.new(happy: v).happy).to eql(false)
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "time" do
138
+ it "converts unix timestamps to DateTime" do
139
+ v = Box.new(birth: 1234567890).birth
140
+ expect(v.class).to eql(DateTime)
141
+ expect(v.to_time.to_i).to eql(1234567890)
142
+ expect(v.year).to eql(2009)
143
+ end
144
+
145
+ it "converts strings DateTime" do
146
+ dt = DateTime.new(2011,2,3,4,5,6)
147
+ v = Box.new(birth: dt.to_s).birth
148
+ expect(v.class).to eql(DateTime)
149
+ expect(v.to_time.to_i).to eql(1296705906)
150
+ expect(v.year).to eql(2011)
151
+ end
152
+ end
153
+
154
+ describe "array" do
155
+ it "defaults to []" do
156
+ v = Box.new.aliases
157
+ expect(v.class).to eql(Array)
158
+ expect(v).to eql([])
159
+ end
160
+
161
+ it "stores arrays as is" do
162
+ v = Box.new(aliases: ["chuck", "charles"]).aliases
163
+ expect(v).to eql(["chuck", "charles"])
164
+ end
165
+
166
+ it "wraps singualr values in an array" do
167
+ v = Box.new(aliases: "chuck").aliases
168
+ expect(v).to eql(["chuck"])
169
+ end
170
+ end
171
+
172
+ describe "hash" do
173
+ it "defaults to {}" do
174
+ v = Box.new.extras
175
+ expect(v.class).to eql(Hash)
176
+ expect(v).to eql({})
177
+ end
178
+
179
+ it "stores hashes as is" do
180
+ v = Box.new(extras: {"a" => 1, "b" => 2}).extras
181
+ expect(v).to eql({"a" => 1, "b" => 2})
182
+ end
183
+
184
+ it "converts nested array values to a hash" do
185
+ v = Box.new(extras: [["type", "person"], ["pet", "dog"]]).extras
186
+ expect(v).to eql({"type" => "person", "pet" => "dog"})
187
+ end
188
+ end
189
+
190
+ describe "model relationships" do
191
+ it "establishes single model relationships" do
192
+ v = Box.new(item: {id: 1}).item
193
+ expect(v).to be_an_instance_of(SpecialItem)
194
+ expect(v.id).to eql(1)
195
+ end
196
+
197
+ it "supports explicit class name" do
198
+ v = Box.new(special: {id: 1}).special
199
+ expect(v).to be_an_instance_of(SuperSpecialItem)
200
+ expect(v.id).to eql(1)
201
+ end
202
+
203
+ it "establishes many model relationships" do
204
+ v = Box.new(items: [{id: 1}, {id: 2}]).items
205
+ expect(v).to be_an_instance_of(Frenchy::Collection)
206
+ expect(v[0]).to be_an_instance_of(SpecialItem)
207
+ expect(v[0].id).to eql(1)
208
+ expect(v[1]).to be_an_instance_of(SpecialItem)
209
+ expect(v[1].id).to eql(2)
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe Frenchy::Request do
4
+ describe "path substitution" do
5
+ it "substitutes path parameters" do
6
+ request = Frenchy::Request.new("service", "get", "/v1/users/:id/:token", {"id" => 1234, "token" => "md5something"}, {})
7
+ expect(request.path).to eql("/v1/users/1234/md5something")
8
+ end
9
+
10
+ it "retains remaining parameters as query parameters" do
11
+ request = Frenchy::Request.new("service", "get", "/v1/users/:id", {"id" => 1234, "token" => "md5something"}, {})
12
+ expect(request.path).to eql("/v1/users/1234")
13
+ expect(request.params).to eql({"token" => "md5something"})
14
+ end
15
+
16
+ it "raises an error for missing path parameters" do
17
+ expect do
18
+ Frenchy::Request.new("service", "get", "/v1/users/:id/:token", {"id" => 1234}, {})
19
+ end.to raise_error(Frenchy::Error)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,105 @@
1
+ require "spec_helper"
2
+
3
+ class Bin
4
+ include Frenchy::Model
5
+ include Frenchy::Resource
6
+
7
+ resource service: "httpbin",
8
+ endpoints: {
9
+ default: { method: "get", path: "/get" },
10
+ one: { method: "get", path: "/get" },
11
+ many: { method: "get", path: "/get" },
12
+ search: { method: "get", path: "/get" },
13
+ }
14
+
15
+ field :args, type: "hash"
16
+ field :headers, type: "hash"
17
+ field :origin, type: "string"
18
+ field :url, type: "string"
19
+ end
20
+
21
+ class BinOneEndpoint
22
+ include Frenchy::Model
23
+ include Frenchy::Resource
24
+
25
+ resource service: "httpbin", endpoint: { path: "/get" }
26
+
27
+ field :args, type: "hash"
28
+ end
29
+
30
+ class BinNoEndpoints
31
+ include Frenchy::Model
32
+ include Frenchy::Resource
33
+
34
+ resource service: "httpbin"
35
+
36
+ field :id, type: "string"
37
+ end
38
+
39
+ class BinArgs
40
+ include Frenchy::Model
41
+ include Frenchy::Resource
42
+
43
+ resource service: "httpbin",
44
+ endpoints: {
45
+ nested: { method: "get", path: "/get", nesting: "args" },
46
+ }
47
+
48
+ field :my_arg, type: "string"
49
+ end
50
+
51
+ describe Frenchy::Resource do
52
+ before :all do
53
+ Frenchy.register_service("httpbin", {"host" => "http://httpbin.org"})
54
+ end
55
+
56
+ describe ".find" do
57
+ it "finds a single object with a single string parameter (id substitution)" do
58
+ resp = Bin.find("a")
59
+ expect(resp.args["id"]).to eql("a")
60
+ end
61
+
62
+ it "finds a single object with a parameters hash" do
63
+ resp = Bin.find(id: "a")
64
+ expect(resp.args["id"]).to eql("a")
65
+ end
66
+ end
67
+
68
+ describe ".find_one" do
69
+ it "finds a single object with id" do
70
+ resp = Bin.find_one("a")
71
+ expect(resp.args["id"]).to eql("a")
72
+ end
73
+ end
74
+
75
+ describe ".find_many" do
76
+ it "finds many objects with ids" do
77
+ resp = Bin.find_many(["a", "b", "c"])
78
+ expect(resp.args["ids"]).to eql("a,b,c")
79
+ end
80
+ end
81
+
82
+ describe ".find_with_endpoint" do
83
+ it "finds a single object with endpoint and params" do
84
+ resp = Bin.find_with_endpoint(:default, a: 1, b: 2)
85
+ expect(resp.args).to eql({"a" => "1", "b" => "2"})
86
+ end
87
+
88
+ it "finds a single object with a single endpoint and params" do
89
+ resp = BinOneEndpoint.find_with_endpoint(:default, a: 1, b: 2)
90
+ expect(resp.args).to eql({"a" => "1", "b" => "2"})
91
+ end
92
+
93
+ it "finds a single object with a nested endpoint and params" do
94
+ resp = BinArgs.find_with_endpoint(:nested, my_arg: "dataz")
95
+ expect(resp).to be_an_instance_of(BinArgs)
96
+ expect(resp.my_arg).to eql("dataz")
97
+ end
98
+
99
+ it "raises an exception if there are no endpoints" do
100
+ expect do
101
+ BinNoEndpoints.find_with_endpoint(:nonexist, myarg: "mydata")
102
+ end.to raise_exception(Frenchy::Error)
103
+ end
104
+ end
105
+ end