1 # -*- coding: utf-8 -*-
2 # Copyright 2013 Google Inc. All Rights Reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 """Integration tests for setmeta command."""
17 from __future__
import absolute_import
19 from gslib
.cs_api_map
import ApiSelector
20 import gslib
.tests
.testcase
as testcase
21 from gslib
.tests
.testcase
.integration_testcase
import SkipForS3
22 from gslib
.tests
.util
import ObjectToURI
as suri
23 from gslib
.util
import Retry
24 from gslib
.util
import UTF8
27 class TestSetMeta(testcase
.GsUtilIntegrationTestCase
):
28 """Integration tests for setmeta command."""
30 def test_initial_metadata(self
):
31 """Tests copying file to an object with metadata."""
32 objuri
= suri(self
.CreateObject(contents
='foo'))
33 inpath
= self
.CreateTempFile()
35 self
.RunGsUtil(['-h', 'x-%s-meta-xyz:abc' % self
.provider_custom_meta
,
36 '-h', 'Content-Type:%s' % ct
, 'cp', inpath
, objuri
])
37 # Use @Retry as hedge against bucket listing eventual consistency.
38 @Retry(AssertionError, tries
=3, timeout_secs
=1)
40 stdout
= self
.RunGsUtil(['ls', '-L', objuri
], return_stdout
=True)
41 self
.assertRegexpMatches(stdout
, r
'Content-Type:\s+%s' % ct
)
42 self
.assertRegexpMatches(stdout
, r
'xyz:\s+abc')
45 def test_overwrite_existing(self
):
46 """Tests overwriting an object's metadata."""
47 objuri
= suri(self
.CreateObject(contents
='foo'))
48 inpath
= self
.CreateTempFile()
49 self
.RunGsUtil(['-h', 'x-%s-meta-xyz:abc' % self
.provider_custom_meta
,
50 '-h', 'Content-Type:image/gif', 'cp', inpath
, objuri
])
51 self
.RunGsUtil(['setmeta', '-h', 'Content-Type:text/html', '-h',
52 'x-%s-meta-xyz' % self
.provider_custom_meta
, objuri
])
53 # Use @Retry as hedge against bucket listing eventual consistency.
54 @Retry(AssertionError, tries
=3, timeout_secs
=1)
56 stdout
= self
.RunGsUtil(['ls', '-L', objuri
], return_stdout
=True)
57 self
.assertRegexpMatches(stdout
, r
'Content-Type:\s+text/html')
58 self
.assertNotIn('xyz', stdout
)
61 @SkipForS3('Preconditions not supported for s3 objects')
62 def test_generation_precondition(self
):
63 """Tests setting metadata with a generation precondition."""
64 object_uri
= self
.CreateObject(contents
='foo')
65 generation
= object_uri
.generation
67 stderr
= self
.RunGsUtil(
68 ['-h', 'x-goog-if-generation-match:%d' % (long(generation
) + 1),
69 'setmeta', '-h', 'x-%s-meta-xyz:abc' % self
.provider_custom_meta
,
70 '-h', 'Content-Type:%s' % ct
, suri(object_uri
)], expected_status
=1,
72 if self
.test_api
== ApiSelector
.XML
:
73 # XML API returns a 400 if the generation does not match some valid one.
74 self
.assertIn('BadRequestException', stderr
)
76 self
.assertIn('Precondition', stderr
)
79 ['-h', 'x-goog-generation-match:%s' % generation
, 'setmeta', '-h',
80 'x-%s-meta-xyz:abc' % self
.provider_custom_meta
,
81 '-h', 'Content-Type:%s' % ct
, suri(object_uri
)])
82 stdout
= self
.RunGsUtil(['ls', '-L', suri(object_uri
)], return_stdout
=True)
83 self
.assertRegexpMatches(stdout
, r
'Content-Type:\s+%s' % ct
)
84 self
.assertRegexpMatches(stdout
, r
'xyz:\s+abc')
86 @SkipForS3('Preconditions not supported for s3 objects')
87 def test_metageneration_precondition(self
):
88 """Tests setting metadata with a metageneration precondition."""
89 object_uri
= self
.CreateObject(contents
='foo')
91 stderr
= self
.RunGsUtil(
92 ['-h', 'x-goog-if-metageneration-match:5', 'setmeta', '-h',
93 'x-%s-meta-xyz:abc' % self
.provider_custom_meta
,
94 '-h', 'Content-Type:%s' % ct
, suri(object_uri
)], expected_status
=1,
96 self
.assertIn('Precondition', stderr
)
98 ['-h', 'x-goog-metageneration-match:1', 'setmeta', '-h',
99 'x-%s-meta-xyz:abc' % self
.provider_custom_meta
,
100 '-h', 'Content-Type:%s' % ct
, suri(object_uri
)])
101 stdout
= self
.RunGsUtil(['ls', '-L', suri(object_uri
)], return_stdout
=True)
102 self
.assertRegexpMatches(stdout
, r
'Content-Type:\s+%s' % ct
)
103 self
.assertRegexpMatches(stdout
, r
'xyz:\s+abc')
105 def test_duplicate_header_removal(self
):
106 stderr
= self
.RunGsUtil(
107 ['setmeta', '-h', 'Content-Type:text/html', '-h', 'Content-Type',
108 'gs://foo/bar'], expected_status
=1, return_stderr
=True)
109 self
.assertIn('Each header must appear at most once', stderr
)
111 def test_duplicate_header(self
):
112 stderr
= self
.RunGsUtil(
113 ['setmeta', '-h', 'Content-Type:text/html', '-h', 'Content-Type:foobar',
114 'gs://foo/bar'], expected_status
=1, return_stderr
=True)
115 self
.assertIn('Each header must appear at most once', stderr
)
117 def test_recursion_works(self
):
118 bucket_uri
= self
.CreateBucket()
119 object1_uri
= self
.CreateObject(bucket_uri
=bucket_uri
, contents
='foo')
120 object2_uri
= self
.CreateObject(bucket_uri
=bucket_uri
, contents
='foo')
121 self
.RunGsUtil(['setmeta', '-R', '-h', 'content-type:footype',
124 for obj_uri
in [object1_uri
, object2_uri
]:
125 stdout
= self
.RunGsUtil(['stat', suri(obj_uri
)], return_stdout
=True)
126 self
.assertIn('footype', stdout
)
128 def test_invalid_non_ascii_custom_header(self
):
129 unicode_header
= u
'x-%s-meta-soufflé:5' % self
.provider_custom_meta
130 unicode_header_bytes
= unicode_header
.encode(UTF8
)
131 stderr
= self
.RunGsUtil(
132 ['setmeta', '-h', unicode_header_bytes
, 'gs://foo/bar'],
133 expected_status
=1, return_stderr
=True)
134 self
.assertIn('Invalid non-ASCII header', stderr
)
136 @SkipForS3('Only ASCII characters are supported for x-amz-meta headers')
137 def test_valid_non_ascii_custom_header(self
):
138 """Tests setting custom metadata with a non-ASCII content."""
139 objuri
= self
.CreateObject(contents
='foo')
140 unicode_header
= u
'x-%s-meta-dessert:soufflé' % self
.provider_custom_meta
141 unicode_header_bytes
= unicode_header
.encode(UTF8
)
142 self
.RunGsUtil(['setmeta', '-h', unicode_header_bytes
, suri(objuri
)])
143 # Use @Retry as hedge against bucket listing eventual consistency.
144 @Retry(AssertionError, tries
=3, timeout_secs
=1)
146 stdout
= self
.RunGsUtil(['ls', '-L', suri(objuri
)], return_stdout
=True)
147 stdout
= stdout
.decode(UTF8
)
148 self
.assertIn(u
'dessert:\t\tsoufflé', stdout
)
151 def test_disallowed_header(self
):
152 stderr
= self
.RunGsUtil(
153 ['setmeta', '-h', 'Content-Length:5', 'gs://foo/bar'],
154 expected_status
=1, return_stderr
=True)
155 self
.assertIn('Invalid or disallowed header', stderr
)
157 def test_setmeta_bucket(self
):
158 bucket_uri
= self
.CreateBucket()
159 stderr
= self
.RunGsUtil(
160 ['setmeta', '-h', 'x-%s-meta-foo:5' % self
.provider_custom_meta
,
161 suri(bucket_uri
)], expected_status
=1, return_stderr
=True)
162 self
.assertIn('must name an object', stderr
)
164 def test_setmeta_invalid_arg(self
):
165 stderr
= self
.RunGsUtil(
166 ['setmeta', '-h', 'foo:bar:baz', 'gs://foo/bar'], expected_status
=1,
168 self
.assertIn('must be either header or header:value', stderr
)
170 def test_setmeta_with_canned_acl(self
):
171 stderr
= self
.RunGsUtil(
172 ['setmeta', '-h', 'x-%s-acl:public-read' % self
.provider_custom_meta
,
173 'gs://foo/bar'], expected_status
=1, return_stderr
=True)
174 self
.assertIn('gsutil setmeta no longer allows canned ACLs', stderr
)
176 def test_invalid_non_ascii_header_value(self
):
177 unicode_header
= u
'Content-Type:dessert/soufflé'
178 unicode_header_bytes
= unicode_header
.encode(UTF8
)
179 stderr
= self
.RunGsUtil(
180 ['setmeta', '-h', unicode_header_bytes
, 'gs://foo/bar'],
181 expected_status
=1, return_stderr
=True)
182 self
.assertIn('Invalid non-ASCII header', stderr
)