payload-api 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/lib/payload/arm/attr.rb +169 -0
- data/lib/payload/arm/object.rb +37 -2
- data/lib/payload/arm/request.rb +57 -7
- data/lib/payload/arm/session.rb +8 -4
- data/lib/payload/exceptions.rb +15 -0
- data/lib/payload/objects.rb +10 -2
- data/lib/payload/version.rb +2 -2
- data/spec/payload/arm/arm_request_query_spec.rb +226 -0
- data/spec/payload/arm/attr_spec.rb +216 -0
- data/spec/payload/arm/object_spec.rb +114 -0
- data/spec/payload/arm/request_format_integration_spec.rb +166 -0
- data/spec/payload/arm/request_spec.rb +38 -1
- data/spec/payload/arm/session_spec.rb +40 -0
- data/spec/payload/exceptions_spec.rb +82 -0
- metadata +8 -2
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "payload"
|
|
4
|
+
require "payload/arm/object"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Payload::ARMRequest do
|
|
7
|
+
describe "#group_by" do
|
|
8
|
+
let(:instance) { described_class.new(Payload::Invoice, nil) }
|
|
9
|
+
|
|
10
|
+
it "appends to group_by and returns self" do
|
|
11
|
+
year_attr = Payload::Attr.new("year", Payload::Attr.new("created_at"))
|
|
12
|
+
year_attr.call
|
|
13
|
+
result = instance.group_by(year_attr, Payload::Attr.status)
|
|
14
|
+
expect(result).to be(instance)
|
|
15
|
+
expect(instance.instance_variable_get(:@group_by).map(&:to_s)).to eq(["year(created_at)", "status"])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "includes group_by in request params when all() is called" do
|
|
19
|
+
Payload::api_key = "test_key"
|
|
20
|
+
instance.instance_variable_set(:@cls, Payload::Invoice)
|
|
21
|
+
|
|
22
|
+
expect(instance).to receive(:_execute_request) do |_http, request|
|
|
23
|
+
query = request.path.split("?")[1]
|
|
24
|
+
expect(query).to include("group_by%5B0%5D=") # group_by[0]=
|
|
25
|
+
expect(query).to include("group_by%5B1%5D=") # group_by[1]=
|
|
26
|
+
QuerySpecMockResponse.new('{"object":"list","values":[]}')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
instance.group_by("year(created_at)", "status").all()
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "#order_by" do
|
|
34
|
+
let(:instance) { described_class.new(Payload::Account, nil) }
|
|
35
|
+
|
|
36
|
+
it "appends to order_by and returns self" do
|
|
37
|
+
result = instance.order_by("created_at", "desc(id)")
|
|
38
|
+
expect(result).to be(instance)
|
|
39
|
+
expect(instance.instance_variable_get(:@order_by)).to eq(["created_at", "desc(id)"])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "includes order_by in request params when all() is called" do
|
|
43
|
+
Payload::api_key = "test_key"
|
|
44
|
+
instance.instance_variable_set(:@cls, Payload::Account)
|
|
45
|
+
|
|
46
|
+
expect(instance).to receive(:_execute_request) do |_http, request|
|
|
47
|
+
query = request.path.split("?")[1]
|
|
48
|
+
expect(query).to include("order_by%5B0%5D=created_at")
|
|
49
|
+
expect(query).to include("order_by%5B1%5D=desc%28id%29")
|
|
50
|
+
QuerySpecMockResponse.new('{"object":"list","values":[]}')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
instance.order_by("created_at", "desc(id)").all()
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe "#limit and #offset" do
|
|
58
|
+
let(:instance) { described_class.new(Payload::Account, nil) }
|
|
59
|
+
|
|
60
|
+
it "sets limit and offset and returns self" do
|
|
61
|
+
result = instance.limit(10).offset(20)
|
|
62
|
+
expect(result).to be(instance)
|
|
63
|
+
expect(instance.instance_variable_get(:@limit)).to eq(10)
|
|
64
|
+
expect(instance.instance_variable_get(:@offset)).to eq(20)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "includes limit and offset in request params when all() is called" do
|
|
68
|
+
Payload::api_key = "test_key"
|
|
69
|
+
instance.instance_variable_set(:@cls, Payload::Account)
|
|
70
|
+
|
|
71
|
+
expect(instance).to receive(:_execute_request) do |_http, request|
|
|
72
|
+
query = request.path.split("?")[1]
|
|
73
|
+
expect(query).to include("limit=10")
|
|
74
|
+
expect(query).to include("offset=20")
|
|
75
|
+
QuerySpecMockResponse.new('{"object":"list","values":[]}')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
instance.limit(10).offset(20).all()
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "#request_params" do
|
|
83
|
+
it "merges filters, filter_objects, group_by, order_by, limit, offset into query params" do
|
|
84
|
+
instance = described_class.new(Payload::Transaction, nil)
|
|
85
|
+
instance.instance_variable_set(:@filters, { "fields" => "id,amount" })
|
|
86
|
+
instance.instance_variable_set(:@group_by, ["status"])
|
|
87
|
+
instance.instance_variable_set(:@order_by, ["desc(created_at)"])
|
|
88
|
+
instance.instance_variable_set(:@limit, 5)
|
|
89
|
+
instance.instance_variable_set(:@offset, 10)
|
|
90
|
+
|
|
91
|
+
filter_obj = Payload::ARMGreaterThan.new(Payload::Attr.amount, 100)
|
|
92
|
+
instance.instance_variable_set(:@filter_objects, [filter_obj])
|
|
93
|
+
|
|
94
|
+
params = instance.request_params
|
|
95
|
+
|
|
96
|
+
expect(params["fields"]).to eq("id,amount")
|
|
97
|
+
expect(params["group_by[0]"]).to eq("status")
|
|
98
|
+
expect(params["order_by[0]"]).to eq("desc(created_at)")
|
|
99
|
+
expect(params["limit"]).to eq("5")
|
|
100
|
+
expect(params["offset"]).to eq("10")
|
|
101
|
+
expect(params[filter_obj.attr]).to eq(filter_obj.opval)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "is encoded as URL query string in _request (url.query = URI.encode_www_form(params))" do
|
|
105
|
+
Payload::api_key = "test_key"
|
|
106
|
+
session = Payload::Session.new("test_key", "https://api.test.com", "v2")
|
|
107
|
+
instance = described_class.new(Payload::Invoice, session)
|
|
108
|
+
instance.select("id", "status").filter_by(session.attr.status == "open").order_by("created_at").limit(5).offset(1)
|
|
109
|
+
|
|
110
|
+
expected_params = instance.request_params.dup
|
|
111
|
+
|
|
112
|
+
expect(instance).to receive(:_execute_request) do |_http, request|
|
|
113
|
+
query_str = request.path.split("?", 2)[1]
|
|
114
|
+
expect(query_str).not_to be_nil
|
|
115
|
+
decoded = URI.decode_www_form(query_str || "").to_h
|
|
116
|
+
expected_params.each do |key, value|
|
|
117
|
+
expect(decoded[key]).to eq(value.to_s)
|
|
118
|
+
end
|
|
119
|
+
expect(decoded).to include("fields" => "id,status", "limit" => "5", "offset" => "1")
|
|
120
|
+
expect(decoded.keys).to include("status", "order_by[0]")
|
|
121
|
+
QuerySpecMockResponse.new('{"object":"list","values":[]}')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
instance.all()
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
describe "#[] (slice)" do
|
|
129
|
+
let(:instance) { described_class.new(Payload::Account, nil) }
|
|
130
|
+
|
|
131
|
+
it "raises TypeError for non-Range key" do
|
|
132
|
+
expect { instance["foo"] }.to raise_error(TypeError, /invalid key or index/)
|
|
133
|
+
expect { instance[5] }.to raise_error(TypeError, /invalid key or index/)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "raises ArgumentError for negative begin" do
|
|
137
|
+
expect { instance[-1..10] }.to raise_error(ArgumentError, /Negative slice indices not supported/)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "raises ArgumentError for negative end" do
|
|
141
|
+
expect { instance[0..-5] }.to raise_error(ArgumentError, /Negative slice indices not supported/)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "calls offset(begin).limit(size).all() for a range and returns result" do
|
|
145
|
+
Payload::api_key = "test_key"
|
|
146
|
+
instance.instance_variable_set(:@cls, Payload::Account)
|
|
147
|
+
|
|
148
|
+
expect(instance).to receive(:_execute_request) do |_http, request|
|
|
149
|
+
query = request.path.split("?")[1]
|
|
150
|
+
expect(query).to include("offset=10")
|
|
151
|
+
expect(query).to include("limit=10")
|
|
152
|
+
QuerySpecMockResponse.new('{"object":"list","values":[{"id":"acct_1","object":"customer"}]}')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
result = instance[10..19]
|
|
156
|
+
expect(result).to be_an(Array)
|
|
157
|
+
expect(result.size).to eq(1)
|
|
158
|
+
expect(result[0].id).to eq("acct_1")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe "#filter_by with pl.attr filter objects" do
|
|
163
|
+
it "extracts ARM filter objects and merges their attr/opval into request params" do
|
|
164
|
+
session = Payload::Session.new("test_key", "https://api.test.com", "v2")
|
|
165
|
+
instance = described_class.new(Payload::Transaction, session)
|
|
166
|
+
pl = session
|
|
167
|
+
filter_expr = (pl.attr.amount > 100) | (pl.attr.amount < 200)
|
|
168
|
+
|
|
169
|
+
instance.filter_by(filter_expr)
|
|
170
|
+
|
|
171
|
+
expect(instance.instance_variable_get(:@filter_objects)).to include(be_a(Payload::ARMFilter))
|
|
172
|
+
params = instance.request_params
|
|
173
|
+
expect(params.keys).to include(filter_expr.attr)
|
|
174
|
+
expect(params[filter_expr.attr]).to eq(filter_expr.opval)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "builds GET request with filter object serialized in query string" do
|
|
178
|
+
Payload::api_key = "test_key"
|
|
179
|
+
session = Payload::Session.new("test_key", "https://api.test.com", "v2")
|
|
180
|
+
instance = described_class.new(Payload::Transaction, session)
|
|
181
|
+
filter_expr = session.attr.amount > 100
|
|
182
|
+
|
|
183
|
+
instance.filter_by(filter_expr)
|
|
184
|
+
|
|
185
|
+
expect(instance).to receive(:_execute_request) do |_http, request|
|
|
186
|
+
query = request.path.split("?")[1]
|
|
187
|
+
expect(query).to include("amount=")
|
|
188
|
+
expect(query).to include("%3E100") # URL-encoded ">100"
|
|
189
|
+
QuerySpecMockResponse.new('{"object":"list","values":[]}')
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
instance.all()
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
describe "#filter_by with multiple filters" do
|
|
197
|
+
it "accumulates multiple filter objects and merges keyword filters into @filters" do
|
|
198
|
+
instance = described_class.new(Payload::Invoice, nil)
|
|
199
|
+
f1 = Payload::ARMEqual.new(Payload::Attr.status, "open")
|
|
200
|
+
f2 = Payload::ARMGreaterThan.new(Payload::Attr.amount, 50)
|
|
201
|
+
|
|
202
|
+
instance.filter_by(f1).filter_by(f2).filter_by(custom_key: "value")
|
|
203
|
+
|
|
204
|
+
fo = instance.instance_variable_get(:@filter_objects)
|
|
205
|
+
expect(fo).to include(f1, f2)
|
|
206
|
+
params = instance.request_params
|
|
207
|
+
expect(params["status"]).to eq("open")
|
|
208
|
+
expect(params["amount"]).to eq(">50")
|
|
209
|
+
expect(instance.instance_variable_get(:@filters)[:custom_key]).to eq("value")
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
class QuerySpecMockResponse
|
|
215
|
+
def initialize(body = '{"object":"list","values":[]}')
|
|
216
|
+
@body = body
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def code
|
|
220
|
+
"200"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def body
|
|
224
|
+
@body
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "payload"
|
|
4
|
+
require "payload/arm/attr"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Payload::AttrRoot do
|
|
7
|
+
let(:root) { described_class.new }
|
|
8
|
+
|
|
9
|
+
describe "attribute access" do
|
|
10
|
+
it "returns an Attr for any method name" do
|
|
11
|
+
expect(root.id).to be_a(Payload::Attr)
|
|
12
|
+
expect(root.id.to_s).to eq("id")
|
|
13
|
+
expect(root.created_at).to be_a(Payload::Attr)
|
|
14
|
+
expect(root.created_at.to_s).to eq("created_at")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "returns an Attr for chained property access (non-callable names stay as nested key)" do
|
|
18
|
+
chained = root.sender.account_id
|
|
19
|
+
expect(chained).to be_a(Payload::Attr)
|
|
20
|
+
expect(chained.to_s).to eq("sender[account_id]")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "treats no-arg access as nested attribute" do
|
|
24
|
+
expect(root.created_at.month.to_s).to eq("created_at[month]")
|
|
25
|
+
expect(root.created_at.year.to_s).to eq("created_at[year]")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "supports pl.attr.created_at(:month) style (function as symbol arg)" do
|
|
29
|
+
expect(root.created_at(:month).to_s).to eq("month(created_at)")
|
|
30
|
+
expect(root.created_at(:year).to_s).to eq("year(created_at)")
|
|
31
|
+
expect(root.amount(:sum).to_s).to eq("sum(amount)")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "supports nested attr with symbol: pl.attr.totals.total(:sum) => sum(totals[total])" do
|
|
35
|
+
expect(root.totals.total(:sum).to_s).to eq("sum(totals[total])")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
RSpec.describe Payload::Attr do
|
|
41
|
+
describe "simple attribute" do
|
|
42
|
+
it "has key and to_s as the param when no parent" do
|
|
43
|
+
attr = Payload::Attr.new("amount")
|
|
44
|
+
expect(attr.key).to eq("amount")
|
|
45
|
+
expect(attr.to_s).to eq("amount")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe "nested attribute" do
|
|
50
|
+
it "builds key and to_s as parent[param]" do
|
|
51
|
+
parent = Payload::Attr.new("totals")
|
|
52
|
+
attr = Payload::Attr.new("total", parent)
|
|
53
|
+
expect(attr.key).to eq("totals[total]")
|
|
54
|
+
expect(attr.to_s).to eq("totals[total]")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "function form (after .call)" do
|
|
59
|
+
it "serializes as name(parent_key) when marked as method" do
|
|
60
|
+
parent = Payload::Attr.new("created_at")
|
|
61
|
+
attr = Payload::Attr.new("month", parent)
|
|
62
|
+
attr.call
|
|
63
|
+
expect(attr.to_s).to eq("month(created_at)")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "serializes nested path in function form" do
|
|
67
|
+
parent = Payload::Attr.new("total", Payload::Attr.new("totals"))
|
|
68
|
+
attr = Payload::Attr.new("sum", parent)
|
|
69
|
+
attr.call
|
|
70
|
+
expect(attr.to_s).to eq("sum(totals[total])")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "raises when chaining after a method" do
|
|
74
|
+
parent = Payload::Attr.new("created_at")
|
|
75
|
+
attr = Payload::Attr.new("month", parent)
|
|
76
|
+
attr.call
|
|
77
|
+
expect { attr.day }.to raise_error(RuntimeError, /cannot get attr of method/)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe "#strip" do
|
|
82
|
+
it "returns to_s.strip so Attr works with request.select(*args, **data) and args.map(&:strip)" do
|
|
83
|
+
inner = Payload::Attr.new("created_at")
|
|
84
|
+
year_attr = Payload::Attr.new("year", inner)
|
|
85
|
+
year_attr.call
|
|
86
|
+
expect(year_attr.strip).to eq("year(created_at)")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe "comparisons (return ARMFilter subclasses)" do
|
|
91
|
+
let(:attr_amount) { Payload::Attr.new("amount") }
|
|
92
|
+
let(:attr_status) { Payload::Attr.new("status") }
|
|
93
|
+
|
|
94
|
+
it "== returns ARMEqual with correct attr and opval" do
|
|
95
|
+
f = attr_status == "processed"
|
|
96
|
+
expect(f).to be_a(Payload::ARMEqual)
|
|
97
|
+
expect(f.attr).to eq("status")
|
|
98
|
+
expect(f.opval).to eq("processed")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "!= returns ARMNotEqual with ! prefix" do
|
|
102
|
+
f = attr_status != "draft"
|
|
103
|
+
expect(f).to be_a(Payload::ARMNotEqual)
|
|
104
|
+
expect(f.attr).to eq("status")
|
|
105
|
+
expect(f.opval).to eq("!draft")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "> returns ARMGreaterThan" do
|
|
109
|
+
f = attr_amount > 100
|
|
110
|
+
expect(f).to be_a(Payload::ARMGreaterThan)
|
|
111
|
+
expect(f.attr).to eq("amount")
|
|
112
|
+
expect(f.opval).to eq(">100")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "< returns ARMLessThan" do
|
|
116
|
+
f = attr_amount < 500
|
|
117
|
+
expect(f).to be_a(Payload::ARMLessThan)
|
|
118
|
+
expect(f.opval).to eq("<500")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it ">= returns ARMGreaterThanEqual" do
|
|
122
|
+
f = attr_amount >= 100
|
|
123
|
+
expect(f).to be_a(Payload::ARMGreaterThanEqual)
|
|
124
|
+
expect(f.opval).to eq(">=100")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "<= returns ARMLessThanEqual" do
|
|
128
|
+
f = attr_amount <= 100
|
|
129
|
+
expect(f).to be_a(Payload::ARMLessThanEqual)
|
|
130
|
+
expect(f.opval).to eq("<=100")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "contains returns ARMContains with ?* op" do
|
|
134
|
+
attr = Payload::Attr.new("description")
|
|
135
|
+
f = attr.contains("INV -")
|
|
136
|
+
expect(f).to be_a(Payload::ARMContains)
|
|
137
|
+
expect(f.attr).to eq("description")
|
|
138
|
+
expect(f.opval).to eq("?*INV -")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "comparisons use Attr key for nested paths" do
|
|
142
|
+
nested = Payload::Attr.new("total", Payload::Attr.new("totals"))
|
|
143
|
+
f = nested > 0
|
|
144
|
+
expect(f.attr).to eq("totals[total]")
|
|
145
|
+
expect(f.opval).to eq(">0")
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "Attr class-level (Payload::Attr.name)" do
|
|
150
|
+
it "returns Attr via method_missing" do
|
|
151
|
+
expect(Payload::Attr.created_at).to be_a(Payload::Attr)
|
|
152
|
+
expect(Payload::Attr.created_at.to_s).to eq("created_at")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
RSpec.describe Payload::ARMFilter do
|
|
158
|
+
describe "#| (OR)" do
|
|
159
|
+
it "combines two filters on the same attribute and returns ARMEqual with joined opval" do
|
|
160
|
+
left = Payload::ARMGreaterThan.new(Payload::Attr.amount, 100)
|
|
161
|
+
right = Payload::ARMLessThan.new(Payload::Attr.amount, 50)
|
|
162
|
+
combined = left | right
|
|
163
|
+
expect(combined).to be_a(Payload::ARMEqual)
|
|
164
|
+
expect(combined.attr).to eq("amount")
|
|
165
|
+
expect(combined.opval).to eq(">100|<50")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "raises TypeError when other is not an ARMFilter" do
|
|
169
|
+
f = Payload::ARMEqual.new(Payload::Attr.status, "active")
|
|
170
|
+
expect { f | "invalid" }.to raise_error(TypeError, /invalid type/)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "raises ArgumentError when attributes differ" do
|
|
174
|
+
f1 = Payload::ARMEqual.new(Payload::Attr.status, "active")
|
|
175
|
+
f2 = Payload::ARMEqual.new(Payload::Attr.type, "payment")
|
|
176
|
+
expect { f1 | f2 }.to raise_error(ArgumentError, /only works on the same attribute/)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
describe "filter subclasses op values" do
|
|
181
|
+
it "ARMEqual has empty op" do
|
|
182
|
+
f = Payload::ARMEqual.new("status", "active")
|
|
183
|
+
expect(f.opval).to eq("active")
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "ARMNotEqual has ! prefix" do
|
|
187
|
+
f = Payload::ARMNotEqual.new("status", "draft")
|
|
188
|
+
expect(f.opval).to eq("!draft")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "ARMGreaterThan has > prefix" do
|
|
192
|
+
f = Payload::ARMGreaterThan.new("amount", 100)
|
|
193
|
+
expect(f.opval).to eq(">100")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "ARMLessThan has < prefix" do
|
|
197
|
+
f = Payload::ARMLessThan.new("amount", 200)
|
|
198
|
+
expect(f.opval).to eq("<200")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "ARMGreaterThanEqual has >= prefix" do
|
|
202
|
+
f = Payload::ARMGreaterThanEqual.new("amount", 100)
|
|
203
|
+
expect(f.opval).to eq(">=100")
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "ARMLessThanEqual has <= prefix" do
|
|
207
|
+
f = Payload::ARMLessThanEqual.new("amount", 100)
|
|
208
|
+
expect(f.opval).to eq("<=100")
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "ARMContains has ?* prefix" do
|
|
212
|
+
f = Payload::ARMContains.new("email", "example.com")
|
|
213
|
+
expect(f.opval).to eq("?*example.com")
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "payload"
|
|
4
|
+
require "payload/arm/object"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Payload::ARMObject do
|
|
7
|
+
describe "method_missing (attribute access)" do
|
|
8
|
+
let(:session) { Payload::Session.new("test_key", "https://api.test.com", "v2") }
|
|
9
|
+
|
|
10
|
+
it "returns value for present key" do
|
|
11
|
+
obj = Payload::Invoice.new({ "id" => "inv_1", "amount" => 100 }, session)
|
|
12
|
+
expect(obj.id).to eq("inv_1")
|
|
13
|
+
expect(obj.amount).to eq(100)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "raises NoMethodError for missing key (strict behavior preserved)" do
|
|
17
|
+
obj = Payload::Invoice.new({ "id" => "inv_1" }, session)
|
|
18
|
+
expect { obj.nonexistent_key }.to raise_error(NoMethodError, /nonexistent_key/)
|
|
19
|
+
expect { obj.amount }.to raise_error(NoMethodError, /amount/)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "strips trailing = for setter-like names and returns value" do
|
|
23
|
+
obj = Payload::Account.new({ "name" => "Acme" }, session)
|
|
24
|
+
expect(obj.name).to eq("Acme")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "#json" do
|
|
29
|
+
let(:session) { Payload::Session.new("test_key", "https://api.test.com", "v2") }
|
|
30
|
+
|
|
31
|
+
it "returns same as to_json" do
|
|
32
|
+
obj = Payload::Invoice.new({ "id" => "inv_1", "object" => "invoice" }, session)
|
|
33
|
+
expect(obj.json).to eq(obj.to_json)
|
|
34
|
+
expect(obj.json).to include("inv_1")
|
|
35
|
+
expect(obj.json).to include("invoice")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe ".order_by, .limit, .offset" do
|
|
40
|
+
it "delegates to ARMRequest and returns chainable request" do
|
|
41
|
+
req = Payload::Invoice.order_by("created_at")
|
|
42
|
+
expect(req).to be_a(Payload::ARMRequest)
|
|
43
|
+
expect(req.instance_variable_get(:@order_by)).to include("created_at")
|
|
44
|
+
|
|
45
|
+
req = Payload::Invoice.limit(10)
|
|
46
|
+
expect(req.instance_variable_get(:@limit)).to eq(10)
|
|
47
|
+
|
|
48
|
+
req = Payload::Invoice.offset(20)
|
|
49
|
+
expect(req.instance_variable_get(:@offset)).to eq(20)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe ".select" do
|
|
54
|
+
it "delegates to ARMRequest and sets fields filter" do
|
|
55
|
+
req = Payload::Invoice.select("id", "amount")
|
|
56
|
+
expect(req).to be_a(Payload::ARMRequest)
|
|
57
|
+
expect(req.instance_variable_get(:@filters)["fields"]).to eq("id,amount")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#[] (bracket access)" do
|
|
62
|
+
let(:session) { Payload::Session.new("test_key", "https://api.test.com", "v2") }
|
|
63
|
+
|
|
64
|
+
it "returns value for string key" do
|
|
65
|
+
obj = Payload::Invoice.new({ "id" => "inv_1", "status" => "open" }, session)
|
|
66
|
+
expect(obj["id"]).to eq("inv_1")
|
|
67
|
+
expect(obj["status"]).to eq("open")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "returns value for symbol key (data keys are stored as strings)" do
|
|
71
|
+
obj = Payload::Invoice.new({ "id" => "inv_1" }, session)
|
|
72
|
+
expect(obj[:id]).to eq("inv_1")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "returns nil for missing key" do
|
|
76
|
+
obj = Payload::Invoice.new({ "id" => "inv_1" }, session)
|
|
77
|
+
expect(obj["missing"]).to be_nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe "#to_json" do
|
|
82
|
+
let(:session) { Payload::Session.new("test_key", "https://api.test.com", "v2") }
|
|
83
|
+
|
|
84
|
+
it "includes @data in JSON output" do
|
|
85
|
+
obj = Payload::Invoice.new({ "id" => "inv_1", "object" => "invoice", "amount" => 99 }, session)
|
|
86
|
+
json = obj.to_json
|
|
87
|
+
expect(json).to include("inv_1")
|
|
88
|
+
expect(json).to include("invoice")
|
|
89
|
+
expect(json).to include("99")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "merges class poly when present" do
|
|
93
|
+
obj = Payload::Payment.new({ "id" => "txn_1", "object" => "transaction", "type" => "payment" }, session)
|
|
94
|
+
json = obj.to_json
|
|
95
|
+
expect(json).to include("payment")
|
|
96
|
+
expect(json).to include("txn_1")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "#respond_to_missing?" do
|
|
101
|
+
let(:session) { Payload::Session.new("test_key", "https://api.test.com", "v2") }
|
|
102
|
+
|
|
103
|
+
it "returns true for key present in data" do
|
|
104
|
+
obj = Payload::Invoice.new({ "id" => "inv_1", "amount" => 100 }, session)
|
|
105
|
+
expect(obj.respond_to?(:id)).to be true
|
|
106
|
+
expect(obj.respond_to?(:amount)).to be true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "returns false for key missing from data (so method_missing will call super)" do
|
|
110
|
+
obj = Payload::Invoice.new({ "id" => "inv_1" }, session)
|
|
111
|
+
expect(obj.respond_to?(:nonexistent_key)).to be false
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|