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