1# Copyright 2015 gRPC authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15require 'spec_helper' 16 17include GRPC::Core 18 19shared_context 'setup: tags' do 20 let(:sent_message) { 'sent message' } 21 let(:reply_text) { 'the reply' } 22 23 def deadline 24 Time.now + 5 25 end 26 27 def server_allows_client_to_proceed(metadata = {}) 28 recvd_rpc = @server.request_call 29 expect(recvd_rpc).to_not eq nil 30 server_call = recvd_rpc.call 31 ops = { CallOps::SEND_INITIAL_METADATA => metadata } 32 server_batch = server_call.run_batch(ops) 33 expect(server_batch.send_metadata).to be true 34 server_call 35 end 36 37 def new_client_call 38 @ch.create_call(nil, nil, '/method', nil, deadline) 39 end 40 41 def ok_status 42 Struct::Status.new(StatusCodes::OK, 'OK') 43 end 44end 45 46shared_examples 'basic GRPC message delivery is OK' do 47 include GRPC::Core 48 include_context 'setup: tags' 49 50 context 'the test channel' do 51 it 'should have a target' do 52 expect(@ch.target).to be_a(String) 53 end 54 end 55 56 context 'a client call' do 57 it 'should have a peer' do 58 expect(new_client_call.peer).to be_a(String) 59 end 60 end 61 62 it 'calls have peer info' do 63 call = new_client_call 64 expect(call.peer).to be_a(String) 65 end 66 67 it 'servers receive requests from clients and can respond' do 68 call = new_client_call 69 server_call = nil 70 71 server_thread = Thread.new do 72 server_call = server_allows_client_to_proceed 73 end 74 75 client_ops = { 76 CallOps::SEND_INITIAL_METADATA => {}, 77 CallOps::SEND_MESSAGE => sent_message, 78 CallOps::SEND_CLOSE_FROM_CLIENT => nil 79 } 80 client_batch = call.run_batch(client_ops) 81 expect(client_batch.send_metadata).to be true 82 expect(client_batch.send_message).to be true 83 expect(client_batch.send_close).to be true 84 85 # confirm the server can read the inbound message 86 server_thread.join 87 server_ops = { 88 CallOps::RECV_MESSAGE => nil 89 } 90 server_batch = server_call.run_batch(server_ops) 91 expect(server_batch.message).to eq(sent_message) 92 server_ops = { 93 CallOps::RECV_CLOSE_ON_SERVER => nil, 94 CallOps::SEND_STATUS_FROM_SERVER => ok_status 95 } 96 server_batch = server_call.run_batch(server_ops) 97 expect(server_batch.send_close).to be true 98 expect(server_batch.send_status).to be true 99 100 # finish the call 101 final_client_batch = call.run_batch( 102 CallOps::RECV_INITIAL_METADATA => nil, 103 CallOps::RECV_STATUS_ON_CLIENT => nil) 104 expect(final_client_batch.metadata).to eq({}) 105 expect(final_client_batch.status.code).to eq(0) 106 end 107 108 it 'responses written by servers are received by the client' do 109 call = new_client_call 110 server_call = nil 111 112 server_thread = Thread.new do 113 server_call = server_allows_client_to_proceed 114 end 115 116 client_ops = { 117 CallOps::SEND_INITIAL_METADATA => {}, 118 CallOps::SEND_MESSAGE => sent_message, 119 CallOps::SEND_CLOSE_FROM_CLIENT => nil 120 } 121 client_batch = call.run_batch(client_ops) 122 expect(client_batch.send_metadata).to be true 123 expect(client_batch.send_message).to be true 124 expect(client_batch.send_close).to be true 125 126 # confirm the server can read the inbound message 127 server_thread.join 128 server_ops = { 129 CallOps::RECV_MESSAGE => nil 130 } 131 server_batch = server_call.run_batch(server_ops) 132 expect(server_batch.message).to eq(sent_message) 133 server_ops = { 134 CallOps::RECV_CLOSE_ON_SERVER => nil, 135 CallOps::SEND_MESSAGE => reply_text, 136 CallOps::SEND_STATUS_FROM_SERVER => ok_status 137 } 138 server_batch = server_call.run_batch(server_ops) 139 expect(server_batch.send_close).to be true 140 expect(server_batch.send_message).to be true 141 expect(server_batch.send_status).to be true 142 143 # finish the call 144 final_client_batch = call.run_batch( 145 CallOps::RECV_INITIAL_METADATA => nil, 146 CallOps::RECV_MESSAGE => nil, 147 CallOps::RECV_STATUS_ON_CLIENT => nil) 148 expect(final_client_batch.metadata).to eq({}) 149 expect(final_client_batch.message).to eq(reply_text) 150 expect(final_client_batch.status.code).to eq(0) 151 end 152 153 it 'compressed messages can be sent and received' do 154 call = new_client_call 155 server_call = nil 156 long_request_str = '0' * 2000 157 long_response_str = '1' * 2000 158 md = { 'grpc-internal-encoding-request' => 'gzip' } 159 160 server_thread = Thread.new do 161 server_call = server_allows_client_to_proceed(md) 162 end 163 164 client_ops = { 165 CallOps::SEND_INITIAL_METADATA => md, 166 CallOps::SEND_MESSAGE => long_request_str, 167 CallOps::SEND_CLOSE_FROM_CLIENT => nil 168 } 169 client_batch = call.run_batch(client_ops) 170 expect(client_batch.send_metadata).to be true 171 expect(client_batch.send_message).to be true 172 expect(client_batch.send_close).to be true 173 174 # confirm the server can read the inbound message 175 server_thread.join 176 server_ops = { 177 CallOps::RECV_MESSAGE => nil 178 } 179 server_batch = server_call.run_batch(server_ops) 180 expect(server_batch.message).to eq(long_request_str) 181 server_ops = { 182 CallOps::RECV_CLOSE_ON_SERVER => nil, 183 CallOps::SEND_MESSAGE => long_response_str, 184 CallOps::SEND_STATUS_FROM_SERVER => ok_status 185 } 186 server_batch = server_call.run_batch(server_ops) 187 expect(server_batch.send_close).to be true 188 expect(server_batch.send_message).to be true 189 expect(server_batch.send_status).to be true 190 191 client_ops = { 192 CallOps::RECV_INITIAL_METADATA => nil, 193 CallOps::RECV_MESSAGE => nil, 194 CallOps::RECV_STATUS_ON_CLIENT => nil 195 } 196 final_client_batch = call.run_batch(client_ops) 197 expect(final_client_batch.metadata).to eq({}) 198 expect(final_client_batch.message).to eq long_response_str 199 expect(final_client_batch.status.code).to eq(0) 200 end 201 202 it 'servers can ignore a client write and send a status' do 203 call = new_client_call 204 server_call = nil 205 206 server_thread = Thread.new do 207 server_call = server_allows_client_to_proceed 208 end 209 210 client_ops = { 211 CallOps::SEND_INITIAL_METADATA => {}, 212 CallOps::SEND_MESSAGE => sent_message, 213 CallOps::SEND_CLOSE_FROM_CLIENT => nil 214 } 215 client_batch = call.run_batch(client_ops) 216 expect(client_batch.send_metadata).to be true 217 expect(client_batch.send_message).to be true 218 expect(client_batch.send_close).to be true 219 220 # confirm the server can read the inbound message 221 the_status = Struct::Status.new(StatusCodes::OK, 'OK') 222 server_thread.join 223 server_ops = { 224 CallOps::SEND_STATUS_FROM_SERVER => the_status 225 } 226 server_batch = server_call.run_batch(server_ops) 227 expect(server_batch.message).to eq nil 228 expect(server_batch.send_status).to be true 229 230 final_client_batch = call.run_batch( 231 CallOps::RECV_INITIAL_METADATA => nil, 232 CallOps::RECV_STATUS_ON_CLIENT => nil) 233 expect(final_client_batch.metadata).to eq({}) 234 expect(final_client_batch.status.code).to eq(0) 235 end 236 237 it 'completes calls by sending status to client and server' do 238 call = new_client_call 239 server_call = nil 240 241 server_thread = Thread.new do 242 server_call = server_allows_client_to_proceed 243 end 244 245 client_ops = { 246 CallOps::SEND_INITIAL_METADATA => {}, 247 CallOps::SEND_MESSAGE => sent_message 248 } 249 client_batch = call.run_batch(client_ops) 250 expect(client_batch.send_metadata).to be true 251 expect(client_batch.send_message).to be true 252 253 # confirm the server can read the inbound message and respond 254 the_status = Struct::Status.new(StatusCodes::OK, 'OK', {}) 255 server_thread.join 256 server_ops = { 257 CallOps::RECV_MESSAGE => nil 258 } 259 server_batch = server_call.run_batch(server_ops) 260 expect(server_batch.message).to eq sent_message 261 server_ops = { 262 CallOps::SEND_MESSAGE => reply_text, 263 CallOps::SEND_STATUS_FROM_SERVER => the_status 264 } 265 server_batch = server_call.run_batch(server_ops) 266 expect(server_batch.send_status).to be true 267 expect(server_batch.send_message).to be true 268 269 # confirm the client can receive the server response and status. 270 client_ops = { 271 CallOps::SEND_CLOSE_FROM_CLIENT => nil, 272 CallOps::RECV_INITIAL_METADATA => nil, 273 CallOps::RECV_MESSAGE => nil, 274 CallOps::RECV_STATUS_ON_CLIENT => nil 275 } 276 final_client_batch = call.run_batch(client_ops) 277 expect(final_client_batch.send_close).to be true 278 expect(final_client_batch.message).to eq reply_text 279 expect(final_client_batch.status).to eq the_status 280 281 # confirm the server can receive the client close. 282 server_ops = { 283 CallOps::RECV_CLOSE_ON_SERVER => nil 284 } 285 final_server_batch = server_call.run_batch(server_ops) 286 expect(final_server_batch.send_close).to be true 287 end 288 289 def client_cancel_test(cancel_proc, expected_code, 290 expected_details) 291 call = new_client_call 292 server_call = nil 293 294 server_thread = Thread.new do 295 server_call = server_allows_client_to_proceed 296 end 297 298 client_ops = { 299 CallOps::SEND_INITIAL_METADATA => {}, 300 CallOps::RECV_INITIAL_METADATA => nil 301 } 302 client_batch = call.run_batch(client_ops) 303 expect(client_batch.send_metadata).to be true 304 expect(client_batch.metadata).to eq({}) 305 306 cancel_proc.call(call) 307 308 server_thread.join 309 server_ops = { 310 CallOps::RECV_CLOSE_ON_SERVER => nil 311 } 312 server_batch = server_call.run_batch(server_ops) 313 expect(server_batch.send_close).to be true 314 315 client_ops = { 316 CallOps::RECV_STATUS_ON_CLIENT => {} 317 } 318 client_batch = call.run_batch(client_ops) 319 320 expect(client_batch.status.code).to be expected_code 321 expect(client_batch.status.details).to eq expected_details 322 end 323 324 it 'clients can cancel a call on the server' do 325 expected_code = StatusCodes::CANCELLED 326 expected_details = 'CANCELLED' 327 cancel_proc = proc { |call| call.cancel } 328 client_cancel_test(cancel_proc, expected_code, expected_details) 329 end 330 331 it 'cancel_with_status unknown status' do 332 code = StatusCodes::UNKNOWN 333 details = 'test unknown reason' 334 cancel_proc = proc { |call| call.cancel_with_status(code, details) } 335 client_cancel_test(cancel_proc, code, details) 336 end 337 338 it 'cancel_with_status unknown status' do 339 code = StatusCodes::FAILED_PRECONDITION 340 details = 'test failed precondition reason' 341 cancel_proc = proc { |call| call.cancel_with_status(code, details) } 342 client_cancel_test(cancel_proc, code, details) 343 end 344end 345 346shared_examples 'GRPC metadata delivery works OK' do 347 include_context 'setup: tags' 348 349 describe 'from client => server' do 350 before(:example) do 351 n = 7 # arbitrary number of metadata 352 diff_keys_fn = proc { |i| [format('k%d', i), format('v%d', i)] } 353 diff_keys = Hash[n.times.collect { |x| diff_keys_fn.call x }] 354 null_vals_fn = proc { |i| [format('k%d', i), format('v\0%d', i)] } 355 null_vals = Hash[n.times.collect { |x| null_vals_fn.call x }] 356 same_keys_fn = proc { |i| [format('k%d', i), [format('v%d', i)] * n] } 357 same_keys = Hash[n.times.collect { |x| same_keys_fn.call x }] 358 symbol_key = { a_key: 'a val' } 359 @valid_metadata = [diff_keys, same_keys, null_vals, symbol_key] 360 @bad_keys = [] 361 @bad_keys << { Object.new => 'a value' } 362 @bad_keys << { 1 => 'a value' } 363 end 364 365 it 'raises an exception if a metadata key is invalid' do 366 @bad_keys.each do |md| 367 call = new_client_call 368 client_ops = { 369 CallOps::SEND_INITIAL_METADATA => md 370 } 371 blk = proc do 372 call.run_batch(client_ops) 373 end 374 expect(&blk).to raise_error 375 end 376 end 377 378 it 'sends all the metadata pairs when keys and values are valid' do 379 @valid_metadata.each do |md| 380 recvd_rpc = nil 381 rcv_thread = Thread.new do 382 recvd_rpc = @server.request_call 383 end 384 385 call = new_client_call 386 client_ops = { 387 CallOps::SEND_INITIAL_METADATA => md, 388 CallOps::SEND_CLOSE_FROM_CLIENT => nil 389 } 390 client_batch = call.run_batch(client_ops) 391 expect(client_batch.send_metadata).to be true 392 393 # confirm the server can receive the client metadata 394 rcv_thread.join 395 expect(recvd_rpc).to_not eq nil 396 recvd_md = recvd_rpc.metadata 397 replace_symbols = Hash[md.each_pair.collect { |x, y| [x.to_s, y] }] 398 expect(recvd_md).to eq(recvd_md.merge(replace_symbols)) 399 400 # finish the call 401 final_server_batch = recvd_rpc.call.run_batch( 402 CallOps::RECV_CLOSE_ON_SERVER => nil, 403 CallOps::SEND_INITIAL_METADATA => nil, 404 CallOps::SEND_STATUS_FROM_SERVER => ok_status) 405 expect(final_server_batch.send_close).to be(true) 406 expect(final_server_batch.send_metadata).to be(true) 407 expect(final_server_batch.send_status).to be(true) 408 409 final_client_batch = call.run_batch( 410 CallOps::RECV_INITIAL_METADATA => nil, 411 CallOps::RECV_STATUS_ON_CLIENT => nil) 412 expect(final_client_batch.metadata).to eq({}) 413 expect(final_client_batch.status.code).to eq(0) 414 end 415 end 416 end 417 418 describe 'from server => client' do 419 before(:example) do 420 n = 7 # arbitrary number of metadata 421 diff_keys_fn = proc { |i| [format('k%d', i), format('v%d', i)] } 422 diff_keys = Hash[n.times.collect { |x| diff_keys_fn.call x }] 423 null_vals_fn = proc { |i| [format('k%d', i), format('v\0%d', i)] } 424 null_vals = Hash[n.times.collect { |x| null_vals_fn.call x }] 425 same_keys_fn = proc { |i| [format('k%d', i), [format('v%d', i)] * n] } 426 same_keys = Hash[n.times.collect { |x| same_keys_fn.call x }] 427 symbol_key = { a_key: 'a val' } 428 @valid_metadata = [diff_keys, same_keys, null_vals, symbol_key] 429 @bad_keys = [] 430 @bad_keys << { Object.new => 'a value' } 431 @bad_keys << { 1 => 'a value' } 432 end 433 434 it 'raises an exception if a metadata key is invalid' do 435 @bad_keys.each do |md| 436 recvd_rpc = nil 437 rcv_thread = Thread.new do 438 recvd_rpc = @server.request_call 439 end 440 441 call = new_client_call 442 # client signals that it's done sending metadata to allow server to 443 # respond 444 client_ops = { 445 CallOps::SEND_INITIAL_METADATA => nil 446 } 447 call.run_batch(client_ops) 448 449 # server gets the invocation 450 rcv_thread.join 451 expect(recvd_rpc).to_not eq nil 452 server_ops = { 453 CallOps::SEND_INITIAL_METADATA => md 454 } 455 blk = proc do 456 recvd_rpc.call.run_batch(server_ops) 457 end 458 expect(&blk).to raise_error 459 460 # cancel the call so the server can shut down immediately 461 call.cancel 462 end 463 end 464 465 it 'sends an empty hash if no metadata is added' do 466 recvd_rpc = nil 467 rcv_thread = Thread.new do 468 recvd_rpc = @server.request_call 469 end 470 471 call = new_client_call 472 # client signals that it's done sending metadata to allow server to 473 # respond 474 client_ops = { 475 CallOps::SEND_INITIAL_METADATA => nil, 476 CallOps::SEND_CLOSE_FROM_CLIENT => nil 477 } 478 client_batch = call.run_batch(client_ops) 479 expect(client_batch.send_metadata).to be true 480 expect(client_batch.send_close).to be true 481 482 # server gets the invocation but sends no metadata back 483 rcv_thread.join 484 expect(recvd_rpc).to_not eq nil 485 server_call = recvd_rpc.call 486 server_ops = { 487 # receive close and send status to finish the call 488 CallOps::RECV_CLOSE_ON_SERVER => nil, 489 CallOps::SEND_INITIAL_METADATA => nil, 490 CallOps::SEND_STATUS_FROM_SERVER => ok_status 491 } 492 srv_batch = server_call.run_batch(server_ops) 493 expect(srv_batch.send_close).to be true 494 expect(srv_batch.send_metadata).to be true 495 expect(srv_batch.send_status).to be true 496 497 # client receives nothing as expected 498 client_ops = { 499 CallOps::RECV_INITIAL_METADATA => nil, 500 # receive status to finish the call 501 CallOps::RECV_STATUS_ON_CLIENT => nil 502 } 503 final_client_batch = call.run_batch(client_ops) 504 expect(final_client_batch.metadata).to eq({}) 505 expect(final_client_batch.status.code).to eq(0) 506 end 507 508 it 'sends all the pairs when keys and values are valid' do 509 @valid_metadata.each do |md| 510 recvd_rpc = nil 511 rcv_thread = Thread.new do 512 recvd_rpc = @server.request_call 513 end 514 515 call = new_client_call 516 # client signals that it's done sending metadata to allow server to 517 # respond 518 client_ops = { 519 CallOps::SEND_INITIAL_METADATA => nil, 520 CallOps::SEND_CLOSE_FROM_CLIENT => nil 521 } 522 client_batch = call.run_batch(client_ops) 523 expect(client_batch.send_metadata).to be true 524 expect(client_batch.send_close).to be true 525 526 # server gets the invocation but sends no metadata back 527 rcv_thread.join 528 expect(recvd_rpc).to_not eq nil 529 server_call = recvd_rpc.call 530 server_ops = { 531 CallOps::RECV_CLOSE_ON_SERVER => nil, 532 CallOps::SEND_INITIAL_METADATA => md, 533 CallOps::SEND_STATUS_FROM_SERVER => ok_status 534 } 535 srv_batch = server_call.run_batch(server_ops) 536 expect(srv_batch.send_close).to be true 537 expect(srv_batch.send_metadata).to be true 538 expect(srv_batch.send_status).to be true 539 540 # client receives nothing as expected 541 client_ops = { 542 CallOps::RECV_INITIAL_METADATA => nil, 543 CallOps::RECV_STATUS_ON_CLIENT => nil 544 } 545 final_client_batch = call.run_batch(client_ops) 546 replace_symbols = Hash[md.each_pair.collect { |x, y| [x.to_s, y] }] 547 expect(final_client_batch.metadata).to eq(replace_symbols) 548 expect(final_client_batch.status.code).to eq(0) 549 end 550 end 551 end 552end 553 554describe 'the http client/server' do 555 before(:example) do 556 server_host = '0.0.0.0:0' 557 @server = new_core_server_for_testing(nil) 558 server_port = @server.add_http2_port(server_host, :this_port_is_insecure) 559 @server.start 560 @ch = Channel.new("0.0.0.0:#{server_port}", nil, :this_channel_is_insecure) 561 end 562 563 after(:example) do 564 @ch.close 565 @server.shutdown_and_notify(deadline) 566 @server.close 567 end 568 569 it_behaves_like 'basic GRPC message delivery is OK' do 570 end 571 572 it_behaves_like 'GRPC metadata delivery works OK' do 573 end 574end 575 576describe 'the secure http client/server' do 577 include_context 'setup: tags' 578 579 def load_test_certs 580 test_root = File.join(File.dirname(__FILE__), 'testdata') 581 files = ['ca.pem', 'server1.key', 'server1.pem'] 582 files.map { |f| File.open(File.join(test_root, f)).read } 583 end 584 585 before(:example) do 586 certs = load_test_certs 587 server_host = '0.0.0.0:0' 588 server_creds = GRPC::Core::ServerCredentials.new( 589 nil, [{ private_key: certs[1], cert_chain: certs[2] }], false) 590 @server = new_core_server_for_testing(nil) 591 server_port = @server.add_http2_port(server_host, server_creds) 592 @server.start 593 args = { Channel::SSL_TARGET => 'foo.test.google.fr' } 594 @ch = Channel.new("0.0.0.0:#{server_port}", args, 595 GRPC::Core::ChannelCredentials.new(certs[0], nil, nil)) 596 end 597 598 after(:example) do 599 @server.shutdown_and_notify(deadline) 600 @server.close 601 end 602 603 it_behaves_like 'basic GRPC message delivery is OK' do 604 end 605 606 it_behaves_like 'GRPC metadata delivery works OK' do 607 end 608 609 def credentials_update_test(creds_update_md) 610 auth_proc = proc { creds_update_md } 611 call_creds = GRPC::Core::CallCredentials.new(auth_proc) 612 613 initial_md_key = 'k2' 614 initial_md_val = 'v2' 615 initial_md = { initial_md_key => initial_md_val } 616 expected_md = creds_update_md.clone 617 fail 'bad test param' unless expected_md[initial_md_key].nil? 618 expected_md[initial_md_key] = initial_md_val 619 620 recvd_rpc = nil 621 rcv_thread = Thread.new do 622 recvd_rpc = @server.request_call 623 end 624 625 call = new_client_call 626 call.set_credentials! call_creds 627 628 client_batch = call.run_batch( 629 CallOps::SEND_INITIAL_METADATA => initial_md, 630 CallOps::SEND_CLOSE_FROM_CLIENT => nil) 631 expect(client_batch.send_metadata).to be true 632 expect(client_batch.send_close).to be true 633 634 # confirm the server can receive the client metadata 635 rcv_thread.join 636 expect(recvd_rpc).to_not eq nil 637 recvd_md = recvd_rpc.metadata 638 replace_symbols = Hash[expected_md.each_pair.collect { |x, y| [x.to_s, y] }] 639 expect(recvd_md).to eq(recvd_md.merge(replace_symbols)) 640 641 credentials_update_test_finish_call(call, recvd_rpc.call) 642 end 643 644 def credentials_update_test_finish_call(client_call, server_call) 645 final_server_batch = server_call.run_batch( 646 CallOps::RECV_CLOSE_ON_SERVER => nil, 647 CallOps::SEND_INITIAL_METADATA => nil, 648 CallOps::SEND_STATUS_FROM_SERVER => ok_status) 649 expect(final_server_batch.send_close).to be(true) 650 expect(final_server_batch.send_metadata).to be(true) 651 expect(final_server_batch.send_status).to be(true) 652 653 final_client_batch = client_call.run_batch( 654 CallOps::RECV_INITIAL_METADATA => nil, 655 CallOps::RECV_STATUS_ON_CLIENT => nil) 656 expect(final_client_batch.metadata).to eq({}) 657 expect(final_client_batch.status.code).to eq(0) 658 end 659 660 it 'modifies metadata with CallCredentials' do 661 credentials_update_test('k1' => 'updated-v1') 662 end 663 664 it 'modifies large metadata with CallCredentials' do 665 val_array = %w( 666 '00000000000000000000000000000000000000000000000000000000000000', 667 '11111111111111111111111111111111111111111111111111111111111111', 668 ) 669 md = { 670 k3: val_array, 671 k4: '0000000000000000000000000000000000000000000000000000000000', 672 keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey5: 'v1' 673 } 674 credentials_update_test(md) 675 end 676end 677