515
516
{"method": "POST", "data": [],
516
517
"expected_uri": uri_base,
517
518
"expected_body": None,
518
"expected_headers": {}}),
519
"expected_headers": []}),
520
521
{"method": "GET", "data": [],
521
522
"expected_uri": uri_base,
522
523
"expected_body": None,
523
"expected_headers": {}}),
524
"expected_headers": []}),
525
526
{"method": "PUT", "data": [],
526
527
"expected_uri": uri_base,
527
528
"expected_body": None,
528
"expected_headers": {}}),
529
"expected_headers": []}),
530
531
{"method": "DELETE", "data": [],
531
532
"expected_uri": uri_base,
532
533
"expected_body": None,
533
"expected_headers": {}}),
534
# With data, PUT, POST, and DELETE requests have their body and extra
535
# headers prepared by encode_multipart_data. For GET requests, the
536
# data is encoded into the query string, and both the request body and
534
"expected_headers": []}),
535
# With data, PUT, POST, and DELETE requests have their body and
536
# extra headers prepared by build_multipart_message and
537
# encode_multipart_message. For GET requests, the data is
538
# encoded into the query string, and both the request body and
537
539
# extra headers are empty.
538
540
("create-with-data",
539
541
{"method": "POST", "data": [("foo", "bar"), ("foo", "baz")],
544
546
{"method": "GET", "data": [("foo", "bar"), ("foo", "baz")],
545
547
"expected_uri": uri_base + "?foo=bar&foo=baz",
546
548
"expected_body": None,
547
"expected_headers": {}}),
549
"expected_headers": []}),
548
550
("update-with-data",
549
551
{"method": "PUT", "data": [("foo", "bar"), ("foo", "baz")],
550
552
"expected_uri": uri_base,
565
567
{"method": "POST", "data": [],
566
568
"expected_uri": uri_base + "?op=something",
567
569
"expected_body": None,
568
"expected_headers": {}}),
570
"expected_headers": []}),
570
572
{"method": "GET", "data": [],
571
573
"expected_uri": uri_base + "?op=something",
572
574
"expected_body": None,
573
"expected_headers": {}}),
575
"expected_headers": []}),
575
577
{"method": "PUT", "data": [],
576
578
"expected_uri": uri_base + "?op=something",
577
579
"expected_body": None,
578
"expected_headers": {}}),
580
"expected_headers": []}),
580
582
{"method": "DELETE", "data": [],
581
583
"expected_uri": uri_base + "?op=something",
582
584
"expected_body": None,
583
"expected_headers": {}}),
584
# With data, PUT, POST, and DELETE requests have their body and extra
585
# headers prepared by encode_multipart_data. For GET requests, the
586
# data is encoded into the query string, and both the request body and
587
# extra headers are empty. The operation is encoded into the query
585
"expected_headers": []}),
586
# With data, PUT, POST, and DELETE requests have their body and
587
# extra headers prepared by build_multipart_message and
588
# encode_multipart_message. For GET requests, the data is
589
# encoded into the query string, and both the request body and
590
# extra headers are empty. The operation is encoded into the
589
592
("create-with-data",
590
593
{"method": "POST", "data": [("foo", "bar"), ("foo", "baz")],
591
594
"expected_uri": uri_base + "?op=something",
595
598
{"method": "GET", "data": [("foo", "bar"), ("foo", "baz")],
596
599
"expected_uri": uri_base + "?op=something&foo=bar&foo=baz",
597
600
"expected_body": None,
598
"expected_headers": {}}),
601
"expected_headers": []}),
599
602
("update-with-data",
600
603
{"method": "PUT", "data": [("foo", "bar"), ("foo", "baz")],
601
604
"expected_uri": uri_base + "?op=something",
619
622
scenarios = scenarios_without_op + scenarios_with_op
621
624
def test_prepare_payload(self):
622
# Patch encode_multipart_data to match the scenarios.
623
encode_multipart_data = self.patch(api, "encode_multipart_data")
624
encode_multipart_data.return_value = sentinel.body, sentinel.headers
625
# Patch build_multipart_message and encode_multipart_message to
626
# match the scenarios.
627
build_multipart_message = self.patch(api, "build_multipart_message")
628
build_multipart_message.return_value = sentinel.message
629
encode_multipart_message = self.patch(api, "encode_multipart_message")
630
encode_multipart_message.return_value = sentinel.headers, sentinel.body
625
631
# The payload returned is a 3-tuple of (uri, body, headers).
626
632
payload = api.Action.prepare_payload(
627
633
op=self.op, method=self.method,
632
638
Equals(self.expected_headers),
634
640
self.assertThat(payload, MatchesListwise(expected))
635
# encode_multipart_data, when called, is passed the data
641
# encode_multipart_message, when called, is passed the data
637
643
if self.expected_body is sentinel.body:
639
api.encode_multipart_data, MockCalledOnceWith(self.data))
645
api.build_multipart_message,
646
MockCalledOnceWith(self.data))
648
api.encode_multipart_message,
649
MockCalledOnceWith(sentinel.message))
652
class TestPayloadPreparationWithFiles(MAASTestCase):
653
"""Tests for `maascli.api.Action.prepare_payload` involving files."""
655
def test_files_are_included(self):
656
parameter = factory.make_name("param")
657
contents = factory.getRandomBytes()
658
filename = self.make_file(contents=contents)
659
# Writing the parameter as "parameter@=filename" on the
660
# command-line causes name_value_pair() to return a `name,
661
# opener` tuple, where `opener` is a callable that returns an
663
data = [(parameter, partial(open, filename, "rb"))]
664
uri, body, headers = api.Action.prepare_payload(
665
op=None, method="POST", uri="http://localhost", data=data)
667
expected_body_template = """\
669
Content-Transfer-Encoding: base64
670
Content-Disposition: form-data; name="%s"; filename="%s"
672
Content-Type: application/octet-stream
677
expected_body = expected_body_template % (
678
parameter, parameter, contents.encode("base64"))
680
self.assertDocTestMatches(expected_body, body)