Daisuke Morita | 8e1f861 | 2013-11-26 15:43:21 +0900 | [diff] [blame^] | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
| 2 | |
| 3 | # Copyright 2013 NTT Corporation |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 6 | # not use this file except in compliance with the License. You may obtain |
| 7 | # a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 14 | # License for the specific language governing permissions and limitations |
| 15 | # under the License. |
| 16 | |
| 17 | import re |
| 18 | |
| 19 | |
| 20 | class ExistsAllResponseHeaders(object): |
| 21 | """ |
| 22 | Specific matcher to check the existence of Swift's response headers |
| 23 | |
| 24 | This matcher checks the existence of common headers for each HTTP method |
| 25 | or the target, which means account, container or object. |
| 26 | When checking the existence of 'specific' headers such as |
| 27 | X-Account-Meta-* or X-Object-Manifest for example, those headers must be |
| 28 | checked in each test code. |
| 29 | """ |
| 30 | |
| 31 | def __init__(self, target, method): |
| 32 | """ |
| 33 | param: target Account/Container/Object |
| 34 | param: method PUT/GET/HEAD/DELETE/COPY/POST |
| 35 | """ |
| 36 | self.target = target |
| 37 | self.method = method |
| 38 | |
| 39 | def match(self, actual): |
| 40 | """ |
| 41 | param: actual HTTP response headers |
| 42 | """ |
| 43 | # Check common headers for all HTTP methods |
| 44 | if 'content-length' not in actual: |
| 45 | return NonExistentHeader('content-length') |
| 46 | if 'content-type' not in actual: |
| 47 | return NonExistentHeader('content-type') |
| 48 | if 'x-trans-id' not in actual: |
| 49 | return NonExistentHeader('x-trans-id') |
| 50 | if 'date' not in actual: |
| 51 | return NonExistentHeader('date') |
| 52 | |
| 53 | # Check headers for a specific method or target |
| 54 | if self.method == 'GET' or self.method == 'HEAD': |
| 55 | if 'x-timestamp' not in actual: |
| 56 | return NonExistentHeader('x-timestamp') |
| 57 | if 'accept-ranges' not in actual: |
| 58 | return NonExistentHeader('accept-ranges') |
| 59 | if self.target == 'Account': |
| 60 | if 'x-account-bytes-used' not in actual: |
| 61 | return NonExistentHeader('x-account-bytes-used') |
| 62 | if 'x-account-container-count' not in actual: |
| 63 | return NonExistentHeader('x-account-container-count') |
| 64 | if 'x-account-object-count' not in actual: |
| 65 | return NonExistentHeader('x-account-object-count') |
| 66 | elif self.target == 'Container': |
| 67 | if 'x-container-bytes-used' not in actual: |
| 68 | return NonExistentHeader('x-container-bytes-used') |
| 69 | if 'x-container-object-count' not in actual: |
| 70 | return NonExistentHeader('x-container-object-count') |
| 71 | elif self.target == 'Object': |
| 72 | if 'etag' not in actual: |
| 73 | return NonExistentHeader('etag') |
| 74 | elif self.method == 'PUT' or self.method == 'COPY': |
| 75 | if self.target == 'Object': |
| 76 | if 'etag' not in actual: |
| 77 | return NonExistentHeader('etag') |
| 78 | |
| 79 | return None |
| 80 | |
| 81 | |
| 82 | class NonExistentHeader(object): |
| 83 | """ |
| 84 | Informs an error message for end users in the case of missing a |
| 85 | certain header in Swift's responses |
| 86 | """ |
| 87 | |
| 88 | def __init__(self, header): |
| 89 | self.header = header |
| 90 | |
| 91 | def describe(self): |
| 92 | return "%s header does not exist" % self.header |
| 93 | |
| 94 | def get_details(self): |
| 95 | return {} |
| 96 | |
| 97 | |
| 98 | class AreAllWellFormatted(object): |
| 99 | """ |
| 100 | Specific matcher to check the correctness of formats of values of Swift's |
| 101 | response headers |
| 102 | |
| 103 | This matcher checks the format of values of response headers. |
| 104 | When checking the format of values of 'specific' headers such as |
| 105 | X-Account-Meta-* or X-Object-Manifest for example, those values must be |
| 106 | checked in each test code. |
| 107 | """ |
| 108 | |
| 109 | def match(self, actual): |
| 110 | for key, value in actual.iteritems(): |
| 111 | if key == 'content-length' and not value.isdigit(): |
| 112 | return InvalidFormat(key, value) |
| 113 | elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value): |
| 114 | return InvalidFormat(key, value) |
| 115 | elif key == 'x-account-bytes-used' and not value.isdigit(): |
| 116 | return InvalidFormat(key, value) |
| 117 | elif key == 'x-account-container-count' and not value.isdigit(): |
| 118 | return InvalidFormat(key, value) |
| 119 | elif key == 'x-account-object-count' and not value.isdigit(): |
| 120 | return InvalidFormat(key, value) |
| 121 | elif key == 'x-container-bytes-used' and not value.isdigit(): |
| 122 | return InvalidFormat(key, value) |
| 123 | elif key == 'x-container-object-count' and not value.isdigit(): |
| 124 | return InvalidFormat(key, value) |
| 125 | elif key == 'content-type' and not value: |
| 126 | return InvalidFormat(key, value) |
| 127 | elif key == 'x-trans-id' and \ |
| 128 | not re.match("^tx[0-9a-f]*-[0-9a-f]*$", value): |
| 129 | return InvalidFormat(key, value) |
| 130 | elif key == 'date' and not value: |
| 131 | return InvalidFormat(key, value) |
| 132 | elif key == 'accept-ranges' and not value == 'bytes': |
| 133 | return InvalidFormat(key, value) |
| 134 | elif key == 'etag' and not value.isalnum(): |
| 135 | return InvalidFormat(key, value) |
| 136 | |
| 137 | return None |
| 138 | |
| 139 | |
| 140 | class InvalidFormat(object): |
| 141 | """ |
| 142 | Informs an error message for end users if a format of a certain header |
| 143 | is invalid |
| 144 | """ |
| 145 | |
| 146 | def __init__(self, key, value): |
| 147 | self.key = key |
| 148 | self.value = value |
| 149 | |
| 150 | def describe(self): |
| 151 | return "InvalidFormat (%s, %s)" % (self.key, self.value) |
| 152 | |
| 153 | def get_details(self): |
| 154 | return {} |