ns_connector 0.0.6
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/Gemfile +13 -0
- data/Gemfile.lock +80 -0
- data/Guardfile +9 -0
- data/HACKING +31 -0
- data/LICENSE.txt +7 -0
- data/README.rdoc +191 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/ns_connector.rb +4 -0
- data/lib/ns_connector/attaching.rb +42 -0
- data/lib/ns_connector/chunked_searching.rb +111 -0
- data/lib/ns_connector/config.rb +66 -0
- data/lib/ns_connector/errors.rb +79 -0
- data/lib/ns_connector/field_store.rb +19 -0
- data/lib/ns_connector/hash.rb +11 -0
- data/lib/ns_connector/resource.rb +288 -0
- data/lib/ns_connector/resources.rb +3 -0
- data/lib/ns_connector/resources/contact.rb +279 -0
- data/lib/ns_connector/resources/customer.rb +355 -0
- data/lib/ns_connector/resources/invoice.rb +466 -0
- data/lib/ns_connector/restlet.rb +137 -0
- data/lib/ns_connector/sublist.rb +21 -0
- data/lib/ns_connector/sublist_item.rb +25 -0
- data/misc/failed_sublist_saving_patch +547 -0
- data/scripts/run_restlet +25 -0
- data/scripts/test_shell +21 -0
- data/spec/attaching_spec.rb +48 -0
- data/spec/chunked_searching_spec.rb +75 -0
- data/spec/config_spec.rb +43 -0
- data/spec/resource_spec.rb +340 -0
- data/spec/resources/contact_spec.rb +8 -0
- data/spec/resources/customer_spec.rb +8 -0
- data/spec/resources/invoice_spec.rb +20 -0
- data/spec/restlet_spec.rb +135 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/sublist_item_spec.rb +25 -0
- data/spec/sublist_spec.rb +45 -0
- data/spec/support/mock_data.rb +10 -0
- data/support/read_only_test +63 -0
- data/support/restlet.js +384 -0
- data/support/super_dangerous_write_test +85 -0
- metadata +221 -0
data/scripts/run_restlet
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.join(
|
3
|
+
File.dirname(__FILE__),
|
4
|
+
'..', 'lib'
|
5
|
+
)
|
6
|
+
|
7
|
+
require 'ns_connector'
|
8
|
+
require 'pp'
|
9
|
+
|
10
|
+
unless ARGV.size == 2 then
|
11
|
+
warn(
|
12
|
+
"Usage: #{$0}"\
|
13
|
+
"<configuration as ruby code> "\
|
14
|
+
"<arguments as ruby code>"
|
15
|
+
)
|
16
|
+
warn('e.g.')
|
17
|
+
warn(
|
18
|
+
"#{$0}"\
|
19
|
+
'"{:account_id => ...}" "{:action => ...}"'
|
20
|
+
)
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
NSConnector::Config.set_config!(eval(ARGV[0]))
|
25
|
+
pp NSConnector::Restlet.execute!(eval(ARGV[1]))
|
data/scripts/test_shell
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.join(
|
3
|
+
File.dirname(__FILE__),
|
4
|
+
'..', 'lib'
|
5
|
+
)
|
6
|
+
|
7
|
+
require 'ns_connector'
|
8
|
+
require 'pry'
|
9
|
+
|
10
|
+
if ARGV.empty? then
|
11
|
+
warn(
|
12
|
+
"Usage: #{$0}"\
|
13
|
+
"<configuration as ruby code>"
|
14
|
+
)
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
NSConnector::Config.set_config!(eval(ARGV[0]))
|
21
|
+
NSConnector.pry
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class MagicResource
|
4
|
+
extend NSConnector::Attaching
|
5
|
+
def initialize upstream_store
|
6
|
+
end
|
7
|
+
def self.type_id
|
8
|
+
'magic'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class MagicTarget
|
13
|
+
extend NSConnector::Attaching
|
14
|
+
def initialize upstream_store
|
15
|
+
end
|
16
|
+
def self.type_id
|
17
|
+
'target'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe NSConnector::Attaching do
|
22
|
+
it 'attach! works' do
|
23
|
+
NSConnector::Restlet.should_receive(:execute!).with({
|
24
|
+
:action => 'attach',
|
25
|
+
:type_id => 'magic',
|
26
|
+
:target_type_id => 'target',
|
27
|
+
:attachee_id => 42,
|
28
|
+
:data => [1,2,3],
|
29
|
+
:attributes => nil,
|
30
|
+
})
|
31
|
+
|
32
|
+
MagicResource.attach!(MagicTarget, 42, [1,2,3])
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
it 'detach! works' do
|
37
|
+
NSConnector::Restlet.should_receive(:execute!).with({
|
38
|
+
:action => 'detach',
|
39
|
+
:type_id => 'magic',
|
40
|
+
:target_type_id => 'target',
|
41
|
+
:attachee_id => 42,
|
42
|
+
:data => [1,2,3]
|
43
|
+
})
|
44
|
+
|
45
|
+
MagicResource.detach!(MagicTarget, 42, [1,2,3])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class MagicResource
|
4
|
+
extend NSConnector::ChunkedSearching
|
5
|
+
def initialize upstream_store
|
6
|
+
end
|
7
|
+
def self.type_id
|
8
|
+
'magic'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe NSConnector::ChunkedSearching do
|
13
|
+
before :all do
|
14
|
+
# Keep things exciting
|
15
|
+
NSConnector::Config[:no_threads] = rand(15) + 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def mock_chunked_search
|
19
|
+
(0..5).each do |i|
|
20
|
+
MagicResource.should_receive(:grab_chunk).
|
21
|
+
with('filters', i).ordered.and_return([1,2,3])
|
22
|
+
end
|
23
|
+
|
24
|
+
MagicResource.should_receive(:grab_chunk).
|
25
|
+
with('filters', 6).ordered.
|
26
|
+
and_raise(NSConnector::Errors::EndChunking, double)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'grabs a single chunk' do
|
30
|
+
NSConnector::Restlet.should_receive(:execute!).
|
31
|
+
with({
|
32
|
+
:action => 'search',
|
33
|
+
:type_id => 'magic',
|
34
|
+
:data => {
|
35
|
+
:filters => 'filters',
|
36
|
+
:chunk => 42
|
37
|
+
}
|
38
|
+
}).and_return(['retval'])
|
39
|
+
|
40
|
+
MagicResource.should_receive(:new).with('retval')
|
41
|
+
expect(MagicResource.grab_chunk('filters', 42)).to be_a(Array)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'listens to the config' do
|
45
|
+
MagicResource.should_receive(:threaded_search_by_chunks).
|
46
|
+
with('filters').ordered.
|
47
|
+
and_return(1)
|
48
|
+
MagicResource.should_receive(:normal_search_by_chunks).
|
49
|
+
with('filters').ordered.
|
50
|
+
and_return(2)
|
51
|
+
|
52
|
+
expect(MagicResource.search_by_chunks('filters')).to eql(1)
|
53
|
+
NSConnector::Config[:use_threads] = false
|
54
|
+
expect(MagicResource.search_by_chunks('filters')).to eql(2)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'does a normal search correctly' do
|
58
|
+
mock_chunked_search
|
59
|
+
|
60
|
+
ret = MagicResource.normal_search_by_chunks('filters')
|
61
|
+
|
62
|
+
expect(ret).to be_a(Array)
|
63
|
+
# 5 iterations returning 3 each
|
64
|
+
expect(ret).to have(6 * 3).things
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'does a threaded search correctly' do
|
68
|
+
mock_chunked_search
|
69
|
+
|
70
|
+
ret = MagicResource.threaded_search_by_chunks('filters')
|
71
|
+
expect(ret).to be_a(Array)
|
72
|
+
expect(ret).to have(6 * 3).things
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe NSConnector::Config do
|
3
|
+
before(:each) do
|
4
|
+
# Reset our 'global' config
|
5
|
+
# Yes, it's kinda not nice to have a global configuration like
|
6
|
+
# this, but it's better than passing the damned thing around
|
7
|
+
# everywhere. Thread safety should be fine for reads.
|
8
|
+
NSConnector::Config.set_config!({})
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'sets a valid config' do
|
12
|
+
NSConnector::Config.set_config!(valid_config)
|
13
|
+
expect(NSConnector::Config.check_valid!).to be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'sets an invalid config' do
|
17
|
+
NSConnector::Config.set_config!(:invalid => true)
|
18
|
+
expect{NSConnector::Config.check_valid!}.to raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'allows reading of keys' do
|
22
|
+
expect(NSConnector::Config[:account_id]).to be_nil
|
23
|
+
|
24
|
+
NSConnector::Config.set_config!(valid_config)
|
25
|
+
|
26
|
+
expect(NSConnector::Config[:account_id]).to eql('account_id')
|
27
|
+
expect(NSConnector::Config['account_id']).to eql('account_id')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'allows writing of keys' do
|
31
|
+
expect(NSConnector::Config[:account_id]).to be_nil
|
32
|
+
NSConnector::Config[:account_id] = 'account_id'
|
33
|
+
expect(NSConnector::Config[:account_id]).to eql('account_id')
|
34
|
+
|
35
|
+
NSConnector::Config['account_id'] = nil
|
36
|
+
expect(NSConnector::Config[:account_id]).to be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'has defaults' do
|
40
|
+
expect(NSConnector::Config['use_threads']).
|
41
|
+
to eql(!!NSConnector::Config['use_threads'])
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include NSConnector
|
3
|
+
|
4
|
+
class PseudoResource < Resource
|
5
|
+
# The NetSuite internal id for the object. For a Contact, it would be
|
6
|
+
# 'contact'
|
7
|
+
@type_id = 'pseudoresource'
|
8
|
+
@fields = ['id', 'firstname', 'lastname']
|
9
|
+
@sublists = {:notes => [:line]}
|
10
|
+
end
|
11
|
+
|
12
|
+
# We create another resource here to ensure no clashes in anything shared.
|
13
|
+
class OtherResource < Resource
|
14
|
+
@type_id = 'otherresource'
|
15
|
+
@fields = ['id', 'fax']
|
16
|
+
@sublists = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
describe PseudoResource do
|
21
|
+
before :each do
|
22
|
+
@p = PseudoResource.new
|
23
|
+
end
|
24
|
+
|
25
|
+
context '#new' do
|
26
|
+
it 'has things we expect' do
|
27
|
+
PseudoResource.type_id.should eql('pseudoresource')
|
28
|
+
PseudoResource.fields.should eql(
|
29
|
+
['id', 'firstname', 'lastname']
|
30
|
+
)
|
31
|
+
PseudoResource.sublists.should eql({:notes => [:line]})
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'automatically loads field ids just once on #new' do
|
35
|
+
PseudoResource.new
|
36
|
+
expect(@p.fields).
|
37
|
+
to eql(['id', 'firstname', 'lastname'])
|
38
|
+
|
39
|
+
OtherResource.new
|
40
|
+
o = OtherResource.new
|
41
|
+
expect(o.fields).to eql(['id', 'fax'])
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'allows us to pass stuff to new' do
|
45
|
+
new_resource = PseudoResource.new(:firstname => 'first')
|
46
|
+
expect(new_resource.firstname).to eql('first')
|
47
|
+
expect(new_resource.lastname).to be_nil
|
48
|
+
expect(new_resource).to_not be_in_netsuite
|
49
|
+
|
50
|
+
in_ns_resource = PseudoResource.new(
|
51
|
+
{:lastname => 'last'}, true
|
52
|
+
)
|
53
|
+
expect(in_ns_resource.lastname).to eql('last')
|
54
|
+
expect(in_ns_resource.firstname).to be_nil
|
55
|
+
expect(in_ns_resource).to be_in_netsuite
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'allows fields to be read and set' do
|
59
|
+
expect(@p.id).to be_nil
|
60
|
+
expect(@p.firstname).to be_nil
|
61
|
+
expect(@p.lastname).to be_nil
|
62
|
+
|
63
|
+
@p.firstname = 'first'
|
64
|
+
expect(@p.firstname).to eql('first')
|
65
|
+
expect(@p.store).to eql({'firstname' => 'first'})
|
66
|
+
|
67
|
+
# Ensure there is no odd behaviour here
|
68
|
+
p2 = PseudoResource.new
|
69
|
+
p2.firstname = 'second'
|
70
|
+
|
71
|
+
expect(@p.firstname).to eql('first')
|
72
|
+
expect(p2.firstname).to eql('second')
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'does not exist in netsuite as it is new' do
|
76
|
+
expect(PseudoResource.new).to_not be_in_netsuite
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'saves as a new object when #save! is called' do
|
80
|
+
ns_reply = {
|
81
|
+
'firstname' => 'Name',
|
82
|
+
'lastname' => 'nothing',
|
83
|
+
'id' => '42'
|
84
|
+
}
|
85
|
+
|
86
|
+
@p.firstname = 'name'
|
87
|
+
|
88
|
+
Restlet.should_receive(:execute!).
|
89
|
+
with({
|
90
|
+
:action => 'create',
|
91
|
+
:type_id => 'pseudoresource',
|
92
|
+
:fields => ['id', 'firstname', 'lastname'],
|
93
|
+
:data => {'firstname' => 'name'}
|
94
|
+
}).
|
95
|
+
once.
|
96
|
+
and_return(ns_reply)
|
97
|
+
|
98
|
+
expect(@p.save!).to eql(true)
|
99
|
+
|
100
|
+
expect(@p.firstname).to eql('Name')
|
101
|
+
expect(@p.lastname).to eql('nothing')
|
102
|
+
expect(@p.id).to eql('42')
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'has a pretty inspect' do
|
106
|
+
expect(@p.inspect).to eql(
|
107
|
+
'#<NSConnector::PseudoResource:nil>'
|
108
|
+
)
|
109
|
+
|
110
|
+
@p.instance_variable_set('@store', {'id' => 1})
|
111
|
+
expect(@p.inspect).to eql(
|
112
|
+
'#<NSConnector::PseudoResource:1>'
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'reading' do
|
118
|
+
it 'retrieves one resource with #find' do
|
119
|
+
Restlet.should_receive(:execute!).
|
120
|
+
with({
|
121
|
+
:action => 'retrieve',
|
122
|
+
:type_id => 'pseudoresource',
|
123
|
+
:fields => [
|
124
|
+
'id',
|
125
|
+
'firstname',
|
126
|
+
'lastname'
|
127
|
+
],
|
128
|
+
:data => {'id' => 42}
|
129
|
+
}).and_return({'firstname' => 'dude man'})
|
130
|
+
|
131
|
+
result = PseudoResource.find(42)
|
132
|
+
expect(result).to be_a(PseudoResource)
|
133
|
+
expect(result.firstname).to eql('dude man')
|
134
|
+
expect(result).to be_in_netsuite
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'retrieves multiple resources with #advanced_search' do
|
138
|
+
expected_filters = [
|
139
|
+
['entityId', nil, 'is', '42'],
|
140
|
+
['email', nil, 'isempty']
|
141
|
+
]
|
142
|
+
|
143
|
+
Restlet.should_receive(:execute!).
|
144
|
+
with({
|
145
|
+
:action => 'search',
|
146
|
+
:type_id => 'pseudoresource',
|
147
|
+
:fields => [
|
148
|
+
'id',
|
149
|
+
'firstname',
|
150
|
+
'lastname'
|
151
|
+
],
|
152
|
+
:data => {:filters => expected_filters}
|
153
|
+
}).and_return([
|
154
|
+
{'id' => 1, 'firstname' => 'unique'},
|
155
|
+
{'id' => 2, 'firstname' => 'two'}
|
156
|
+
])
|
157
|
+
|
158
|
+
result = PseudoResource.advanced_search([
|
159
|
+
['entityId', nil, 'is', '42'],
|
160
|
+
['email', nil, 'isempty']
|
161
|
+
])
|
162
|
+
expect(result).to be_a(Array)
|
163
|
+
expect(result).to have(2).things
|
164
|
+
|
165
|
+
expect(result.first).to be_a(PseudoResource)
|
166
|
+
expect(result.first.firstname).to eql('unique')
|
167
|
+
expect(result.first.id).to eql(1)
|
168
|
+
expect(result.first).to be_in_netsuite
|
169
|
+
|
170
|
+
expect(result.last.firstname).to eql('two')
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'has convenience method #search_by' do
|
174
|
+
PseudoResource.
|
175
|
+
should_receive(:advanced_search).
|
176
|
+
with([['a', nil, 'is', 'b']]).
|
177
|
+
and_return('nothing')
|
178
|
+
expect(PseudoResource.search_by('a', 'b')).
|
179
|
+
to eql('nothing')
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'has convenience method #all' do
|
183
|
+
PseudoResource.
|
184
|
+
should_receive(:advanced_search).
|
185
|
+
with([]).
|
186
|
+
and_return('nothing')
|
187
|
+
expect(PseudoResource.all).to eql('nothing')
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'tries to chunk given BeginChunking' do
|
191
|
+
Restlet.should_receive(:execute!).
|
192
|
+
and_raise(Errors::BeginChunking, double)
|
193
|
+
PseudoResource.should_receive(:search_by_chunks).
|
194
|
+
and_return('hai')
|
195
|
+
expect(PseudoResource.all).to eql('hai')
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
shared_context 'found_resource' do
|
200
|
+
before :each do
|
201
|
+
Restlet.should_receive(:execute!).
|
202
|
+
with({
|
203
|
+
:action => 'retrieve',
|
204
|
+
:type_id => 'pseudoresource',
|
205
|
+
:fields => [
|
206
|
+
'id',
|
207
|
+
'firstname',
|
208
|
+
'lastname'
|
209
|
+
],
|
210
|
+
:data => {'id' => 42}
|
211
|
+
}).and_return(
|
212
|
+
{
|
213
|
+
'id' => '1',
|
214
|
+
'firstname' => 'orig',
|
215
|
+
'lastname' => 'orig'
|
216
|
+
}
|
217
|
+
)
|
218
|
+
@found_resource = PseudoResource.find(42)
|
219
|
+
expect(@found_resource.firstname).to eql('orig')
|
220
|
+
expect(@found_resource).to be_in_netsuite
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'updating' do
|
225
|
+
include_context 'found_resource'
|
226
|
+
|
227
|
+
it 'updates existing record in NetSuite' do
|
228
|
+
@found_resource.firstname = 'new'
|
229
|
+
expect(@found_resource.firstname).to eql('new')
|
230
|
+
|
231
|
+
Restlet.should_receive(:execute!).
|
232
|
+
with({
|
233
|
+
:action => 'update',
|
234
|
+
:type_id => 'pseudoresource',
|
235
|
+
:fields => [
|
236
|
+
'id',
|
237
|
+
'firstname',
|
238
|
+
'lastname'
|
239
|
+
],
|
240
|
+
:data => {
|
241
|
+
'firstname' => 'new',
|
242
|
+
'lastname' => 'orig',
|
243
|
+
'id' => '1'
|
244
|
+
}
|
245
|
+
}).
|
246
|
+
once.
|
247
|
+
and_return({'firstname' => 'New'})
|
248
|
+
|
249
|
+
@found_resource.save!
|
250
|
+
|
251
|
+
expect(@found_resource.lastname).to be_nil
|
252
|
+
expect(@found_resource.firstname).to eql('New')
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'deleting' do
|
257
|
+
include_context 'found_resource'
|
258
|
+
|
259
|
+
it 'tries to delete ID via class method' do
|
260
|
+
Restlet.should_receive(:execute!).
|
261
|
+
with({
|
262
|
+
:action => 'delete',
|
263
|
+
:type_id => 'pseudoresource',
|
264
|
+
:data => {
|
265
|
+
'id' => 42
|
266
|
+
}
|
267
|
+
}).
|
268
|
+
once.and_return([])
|
269
|
+
PseudoResource.delete!(42)
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'tries to delete ID via instance method' do
|
273
|
+
Restlet.should_receive(:execute!).
|
274
|
+
with({
|
275
|
+
:action => 'delete',
|
276
|
+
:type_id => 'pseudoresource',
|
277
|
+
:data => {
|
278
|
+
'id' => 1
|
279
|
+
}
|
280
|
+
}).
|
281
|
+
once.and_return([])
|
282
|
+
@found_resource.delete!
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'returns false trying to delete an object not in NS' do
|
286
|
+
expect(@p.delete!).to be_false
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context 'sublists on new object' do
|
291
|
+
it 'is empty' do
|
292
|
+
p = PseudoResource.new
|
293
|
+
expect(p.notes).to be_empty
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
context 'raw search' do
|
298
|
+
it 'has #raw_search' do
|
299
|
+
Restlet.should_receive(:execute!).
|
300
|
+
with({
|
301
|
+
:action => 'raw_search',
|
302
|
+
:type_id => 'pseudoresource',
|
303
|
+
:fields => [
|
304
|
+
'id', 'firstname', 'lastname'
|
305
|
+
],
|
306
|
+
:data => {
|
307
|
+
:columns => [['a']],
|
308
|
+
:filters => [['b']]
|
309
|
+
}
|
310
|
+
}).
|
311
|
+
once.and_return(['hai'])
|
312
|
+
expect(PseudoResource.raw_search([['a']], [['b']])).
|
313
|
+
to eql(['hai'])
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context 'link aliases' do
|
318
|
+
include_context 'found_resource'
|
319
|
+
|
320
|
+
it 'has #link' do
|
321
|
+
PseudoResource.should_receive(:attach!).
|
322
|
+
with(OtherResource, '1', [1,2], nil)
|
323
|
+
@found_resource.attach!(OtherResource, [1,2])
|
324
|
+
|
325
|
+
expect{@p.attach!(OtherResource, [1,2])}.to raise_error(
|
326
|
+
::ArgumentError, /need an id/i
|
327
|
+
)
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'has #unlink' do
|
331
|
+
PseudoResource.should_receive(:detach!).
|
332
|
+
with(OtherResource, '1', [1,2])
|
333
|
+
@found_resource.detach!(OtherResource, [1,2])
|
334
|
+
|
335
|
+
expect{@p.detach!(OtherResource, [1,2])}.to raise_error(
|
336
|
+
::ArgumentError, /need an id/i
|
337
|
+
)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|