1use strict;
2use warnings;
3use RT::Test::REST2 tests => undef;
4use Test::Deep;
5
6my $mech = RT::Test::REST2->mech;
7
8my $auth = RT::Test::REST2->authorization_header;
9my $rest_base_path = '/REST/2.0';
10my $user = RT::Test::REST2->user;
11
12my $catalog = RT::Catalog->new( RT->SystemUser );
13$catalog->Load('General assets');
14$catalog->Create(Name => 'General assets') if !$catalog->Id;
15ok($catalog->Id, "General assets catalog");
16
17my $single_cf = RT::CustomField->new( RT->SystemUser );
18my ($ok, $msg) = $single_cf->Create( Name => 'Single', Type => 'FreeformSingle', LookupType => RT::Asset->CustomFieldLookupType);
19ok($ok, $msg);
20my $single_cf_id = $single_cf->Id;
21
22($ok, $msg) = $single_cf->AddToObject($catalog);
23ok($ok, $msg);
24
25my $multi_cf = RT::CustomField->new( RT->SystemUser );
26($ok, $msg) = $multi_cf->Create( Name => 'Multi', Type => 'FreeformMultiple', LookupType => RT::Asset->CustomFieldLookupType);
27ok($ok, $msg);
28my $multi_cf_id = $multi_cf->Id;
29
30($ok, $msg) = $multi_cf->AddToObject($catalog);
31ok($ok, $msg);
32
33# Asset Creation with no ModifyCustomField
34my ($asset_url, $asset_id);
35{
36    my $payload = {
37        Name    => 'Asset creation using REST',
38        Catalog => 'General assets',
39        CustomFields => {
40            $single_cf_id => 'Hello world!',
41        },
42    };
43
44    # Rights Test - No CreateAsset
45    my $res = $mech->post_json("$rest_base_path/asset",
46        $payload,
47        'Authorization' => $auth,
48    );
49    is($res->code, 403);
50    my $content = $mech->json_response;
51    is($content->{message}, 'Permission Denied', "can't create Asset with custom fields you can't set");
52
53    # Rights Test - With CreateAsset
54    $user->PrincipalObj->GrantRight( Right => 'CreateAsset' );
55    $res = $mech->post_json("$rest_base_path/asset",
56        $payload,
57        'Authorization' => $auth,
58    );
59    is($res->code, 400);
60
61    delete $payload->{CustomFields};
62
63    $res = $mech->post_json("$rest_base_path/asset",
64        $payload,
65        'Authorization' => $auth,
66    );
67    is($res->code, 201);
68    ok($asset_url = $res->header('location'));
69    ok(($asset_id) = $asset_url =~ qr[/asset/(\d+)]);
70}
71
72# Asset Display
73{
74    # Rights Test - No ShowAsset
75    my $res = $mech->get($asset_url,
76        'Authorization' => $auth,
77    );
78    is($res->code, 403);
79}
80
81# Rights Test - With ShowAsset but no SeeCustomField
82{
83    $user->PrincipalObj->GrantRight( Right => 'ShowAsset' );
84
85    my $res = $mech->get($asset_url,
86        'Authorization' => $auth,
87    );
88    is($res->code, 200);
89
90    my $content = $mech->json_response;
91    is($content->{id}, $asset_id);
92    is($content->{Status}, 'new');
93    is($content->{Name}, 'Asset creation using REST');
94    is_deeply($content->{'CustomFields'}, [], 'Asset custom field not present');
95    is_deeply([grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }], [], 'No CF hypermedia');
96}
97
98my $no_asset_cf_values = bag(
99  { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
100  { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
101);
102
103# Rights Test - With ShowAsset and SeeCustomField
104{
105    $user->PrincipalObj->GrantRight( Right => 'SeeCustomField');
106
107    my $res = $mech->get($asset_url,
108        'Authorization' => $auth,
109    );
110    is($res->code, 200);
111
112    my $content = $mech->json_response;
113    is($content->{id}, $asset_id);
114    is($content->{Status}, 'new');
115    is($content->{Name}, 'Asset creation using REST');
116    cmp_deeply($content->{CustomFields}, $no_asset_cf_values, 'No asset custom field values');
117
118
119    cmp_deeply(
120        [grep { $_->{ref} eq 'customfield' } @{ $content->{'_hyperlinks'} }],
121        [{
122            ref => 'customfield',
123            id  => $single_cf_id,
124            name => 'Single',
125            type => 'customfield',
126            _url => re(qr[$rest_base_path/customfield/$single_cf_id$]),
127        }, {
128            ref => 'customfield',
129            id  => $multi_cf_id,
130            name => 'Multi',
131            type => 'customfield',
132            _url => re(qr[$rest_base_path/customfield/$multi_cf_id$]),
133        }],
134        'Two CF hypermedia',
135    );
136
137    my ($single_url) = map { $_->{_url} } grep { $_->{ref} eq 'customfield' && $_->{id} == $single_cf_id } @{ $content->{'_hyperlinks'} };
138    my ($multi_url) = map { $_->{_url} } grep { $_->{ref} eq 'customfield' && $_->{id} == $multi_cf_id } @{ $content->{'_hyperlinks'} };
139
140    $res = $mech->get($single_url,
141        'Authorization' => $auth,
142    );
143    is($res->code, 200);
144    cmp_deeply($mech->json_response, superhashof({
145        id         => $single_cf_id,
146        Disabled   => 0,
147        LookupType => RT::Asset->CustomFieldLookupType,
148        MaxValues  => 1,
149    Name       => 'Single',
150    Type       => 'Freeform',
151    }), 'single cf');
152
153    $res = $mech->get($multi_url,
154        'Authorization' => $auth,
155    );
156    is($res->code, 200);
157    cmp_deeply($mech->json_response, superhashof({
158        id         => $multi_cf_id,
159        Disabled   => 0,
160        LookupType => RT::Asset->CustomFieldLookupType,
161        MaxValues  => 0,
162    Name       => 'Multi',
163    Type       => 'Freeform',
164    }), 'multi cf');
165}
166
167# Asset Update without ModifyCustomField
168{
169    my $payload = {
170        Name     => 'Asset update using REST',
171        Status   => 'allocated',
172        CustomFields => {
173            $single_cf_id => 'Modified CF',
174        },
175    };
176
177    # Rights Test - No ModifyAsset
178    my $res = $mech->put_json($asset_url,
179        $payload,
180        'Authorization' => $auth,
181    );
182    TODO: {
183        local $TODO = "RT ->Update isn't introspectable";
184        is($res->code, 403);
185    };
186    is_deeply($mech->json_response, ['Asset Asset creation using REST: Permission Denied', 'Asset Asset creation using REST: Permission Denied', 'Could not add new custom field value: Permission Denied']);
187
188    $user->PrincipalObj->GrantRight( Right => 'ModifyAsset' );
189
190    $res = $mech->put_json($asset_url,
191        $payload,
192        'Authorization' => $auth,
193    );
194    is($res->code, 200);
195    is_deeply($mech->json_response, ["Asset Asset update using REST: Name changed from 'Asset creation using REST' to 'Asset update using REST'", "Asset Asset update using REST: Status changed from 'new' to 'allocated'", 'Could not add new custom field value: Permission Denied']);
196
197    $res = $mech->get($asset_url,
198        'Authorization' => $auth,
199    );
200    is($res->code, 200);
201
202    my $content = $mech->json_response;
203    is($content->{Name}, 'Asset update using REST');
204    is($content->{Status}, 'allocated');
205    cmp_deeply($content->{CustomFields}, $no_asset_cf_values, 'No update to CF');
206}
207
208# Asset Update with ModifyCustomField
209{
210    $user->PrincipalObj->GrantRight( Right => 'ModifyCustomField' );
211    my $payload = {
212        Name  => 'More updates using REST',
213        Status => 'in-use',
214        CustomFields => {
215            $single_cf_id => 'Modified CF',
216        },
217    };
218    my $res = $mech->put_json($asset_url,
219        $payload,
220        'Authorization' => $auth,
221    );
222    is($res->code, 200);
223    is_deeply($mech->json_response, ["Asset More updates using REST: Name changed from 'Asset update using REST' to 'More updates using REST'", "Asset More updates using REST: Status changed from 'allocated' to 'in-use'", 'Single Modified CF added']);
224
225    $res = $mech->get($asset_url,
226        'Authorization' => $auth,
227    );
228    is($res->code, 200);
229
230    my $modified_asset_cf_values = bag(
231        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified CF'] },
232        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
233    );
234
235    my $content = $mech->json_response;
236    is($content->{Name}, 'More updates using REST');
237    is($content->{Status}, 'in-use');
238    cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'New CF value');
239
240    # make sure changing the CF doesn't add a second OCFV
241    $payload->{CustomFields}{$single_cf_id} = 'Modified Again';
242    $res = $mech->put_json($asset_url,
243        $payload,
244        'Authorization' => $auth,
245    );
246    is($res->code, 200);
247    is_deeply($mech->json_response, ['Single Modified CF changed to Modified Again']);
248
249    $res = $mech->get($asset_url,
250        'Authorization' => $auth,
251    );
252    is($res->code, 200);
253
254    $modified_asset_cf_values = bag(
255        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Modified Again'] },
256        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
257    );
258
259    $content = $mech->json_response;
260    cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'New CF value');
261
262    # stop changing the CF, change something else, make sure CF sticks around
263    delete $payload->{CustomFields}{$single_cf_id};
264    $payload->{Name} = 'No CF change';
265    $res = $mech->put_json($asset_url,
266        $payload,
267        'Authorization' => $auth,
268    );
269    is($res->code, 200);
270    is_deeply($mech->json_response, ["Asset No CF change: Name changed from 'More updates using REST' to 'No CF change'"]);
271
272    $res = $mech->get($asset_url,
273        'Authorization' => $auth,
274    );
275    is($res->code, 200);
276
277    $content = $mech->json_response;
278    cmp_deeply($content->{CustomFields}, $modified_asset_cf_values, 'Same CF value');
279}
280
281# Asset Creation with ModifyCustomField
282{
283    my $payload = {
284        Name    => 'Asset creation using REST',
285        Catalog => 'General assets',
286        CustomFields => {
287            $single_cf_id => 'Hello world!',
288        },
289    };
290
291    my $res = $mech->post_json("$rest_base_path/asset",
292        $payload,
293        'Authorization' => $auth,
294    );
295    is($res->code, 201);
296    ok($asset_url = $res->header('location'));
297    ok(($asset_id) = $asset_url =~ qr[/asset/(\d+)]);
298}
299
300# Rights Test - With ShowAsset and SeeCustomField
301{
302    my $res = $mech->get($asset_url,
303        'Authorization' => $auth,
304    );
305    is($res->code, 200);
306
307    my $asset_cf_values = bag(
308        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => ['Hello world!'] },
309        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => [] },
310    );
311
312    my $content = $mech->json_response;
313    is($content->{id}, $asset_id);
314    is($content->{Status}, 'new');
315    is($content->{Name}, 'Asset creation using REST');
316    cmp_deeply($content->{'CustomFields'}, $asset_cf_values, 'Asset custom field');
317}
318
319# Asset Creation for multi-value CF
320for my $value (
321    'scalar',
322    ['array reference'],
323    ['multiple', 'values'],
324) {
325    my $payload = {
326        Name => 'Multi-value CF',
327        Catalog => 'General assets',
328        CustomFields => {
329            $multi_cf_id => $value,
330        },
331    };
332
333    my $res = $mech->post_json("$rest_base_path/asset",
334        $payload,
335        'Authorization' => $auth,
336    );
337    is($res->code, 201);
338    ok($asset_url = $res->header('location'));
339    ok(($asset_id) = $asset_url =~ qr[/asset/(\d+)]);
340
341    $res = $mech->get($asset_url,
342        'Authorization' => $auth,
343    );
344    is($res->code, 200);
345
346    my $content = $mech->json_response;
347    is($content->{id}, $asset_id);
348    is($content->{Status}, 'new');
349    is($content->{Name}, 'Multi-value CF');
350
351    my $output = ref($value) ? $value : [$value]; # scalar input comes out as array reference
352    my $asset_cf_values = bag(
353        { name => 'Single', id => $single_cf_id, type => 'customfield', _url => ignore(), values => [] },
354        { name => 'Multi',  id => $multi_cf_id,  type => 'customfield', _url => ignore(), values => $output },
355    );
356
357    cmp_deeply($content->{'CustomFields'}, $asset_cf_values, 'Asset custom field');
358}
359
360{
361    sub modify_multi_ok {
362        local $Test::Builder::Level = $Test::Builder::Level + 1;
363        my $input = shift;
364        my $messages = shift;
365        my $output = shift;
366        my $name = shift;
367
368        my $payload = {
369            CustomFields => {
370                $multi_cf_id => $input,
371            },
372        };
373        my $res = $mech->put_json($asset_url,
374            $payload,
375            'Authorization' => $auth,
376        );
377        is($res->code, 200);
378        is_deeply($mech->json_response, $messages);
379
380        $res = $mech->get($asset_url,
381            'Authorization' => $auth,
382        );
383        is($res->code, 200);
384
385        my $content = $mech->json_response;
386        my $values;
387        for my $cf (@{ $content->{CustomFields} }) {
388            next unless $cf->{id} == $multi_cf_id;
389
390            $values = [ sort @{ $cf->{values} } ];
391        }
392        cmp_deeply($values, $output, $name || 'New CF value');
393    }
394
395    # starting point: ['multiple', 'values'],
396    modify_multi_ok(['multiple', 'values'], [], ['multiple', 'values'], 'no change');
397    modify_multi_ok(['multiple', 'values', 'new'], ['new added as a value for Multi'], ['multiple', 'new', 'values'], 'added "new"');
398    modify_multi_ok(['multiple', 'new'], ['values is no longer a value for custom field Multi'], ['multiple', 'new'], 'removed "values"');
399    modify_multi_ok('replace all', ['replace all added as a value for Multi', 'multiple is no longer a value for custom field Multi', 'new is no longer a value for custom field Multi'], ['replace all'], 'replaced all values');
400    modify_multi_ok([], ['replace all is no longer a value for custom field Multi'], [], 'removed all values');
401
402    modify_multi_ok(['foo', 'foo', 'bar'], ['foo added as a value for Multi', undef, 'bar added as a value for Multi'], ['bar', 'foo'], 'multiple values with the same name');
403    modify_multi_ok(['foo', 'bar'], [], ['bar', 'foo'], 'multiple values with the same name');
404    modify_multi_ok(['bar'], ['foo is no longer a value for custom field Multi'], ['bar'], 'multiple values with the same name');
405    modify_multi_ok(['bar', 'bar', 'bar'], [undef, undef], ['bar'], 'multiple values with the same name');
406}
407
408done_testing;
409
410