Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@sugarlabs.org>2013-08-09 05:37:50 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2013-08-09 05:37:50 (GMT)
commit6a59ba5d31c15fabc28b3a2fb1aeab0e1991468e (patch)
tree9c379d9e3160fe25f37f48b4ecaef9a1c583d557
parent3917eb055920d878a1a21f8f5170baffb44c39fa (diff)
Create context while uploading initial implementation
-rwxr-xr-xsugar-network30
-rw-r--r--sugar_network/client/routes.py7
-rw-r--r--sugar_network/db/directory.py2
-rw-r--r--sugar_network/db/resource.py3
-rw-r--r--sugar_network/db/routes.py11
-rw-r--r--sugar_network/node/routes.py40
-rw-r--r--sugar_network/toolkit/http.py3
-rw-r--r--sugar_network/toolkit/router.py149
-rwxr-xr-xtests/units/client/online_routes.py3
-rwxr-xr-xtests/units/db/routes.py4
-rwxr-xr-xtests/units/node/node.py76
-rwxr-xr-xtests/units/toolkit/router.py44
12 files changed, 248 insertions, 124 deletions
diff --git a/sugar-network b/sugar-network
index a3e233a..d48c58e 100755
--- a/sugar-network
+++ b/sugar-network
@@ -135,18 +135,21 @@ class Application(application.Application):
path = self.args.pop(0)
enforce(isfile(path), 'Cannot open bundle')
- props = {'tags': []}
- self._parse_args(props['tags'], props)
+ props = {}
+ self._parse_args(props)
if 'license' in props:
value = [i for i in _LIST_RE.split(props['license'].strip()) if i]
props['license'] = value
- guid = self._connect().upload(['implementation'], path, cmd='release')
+ conn = self._connect()
+ # XXX Have to proceed auth before uploading data
+ conn.get(cmd='whoami')
+ guid = conn.upload(['implementation'], path, cmd='release', **props)
if porcelain.value:
print guid
else:
- print dumps(guid)
+ print '-- Uploaded %s implementaion' % guid
@application.command(
'send raw API POST request; '
@@ -198,7 +201,7 @@ class Application(application.Application):
def head(self):
request = Request()
self._parse_path(request)
- self._parse_args([], request)
+ self._parse_args(request)
result = self._connect().meta(request.path, request.query)
self._dump(result, [])
@@ -240,8 +243,7 @@ class Application(application.Application):
pass
self._parse_path(request)
- reply = []
- self._parse_args(reply, request)
+ self._parse_args(request)
pid_path = None
server = None
@@ -267,7 +269,7 @@ class Application(application.Application):
return
if response.content_type == 'application/json':
- self._dump(result, reply)
+ self._dump(result, reply.get('reply') or ['guid'])
elif response.content_type == 'text/event-stream':
while True:
chunk = toolkit.readline(result)
@@ -290,16 +292,16 @@ class Application(application.Application):
if self.args and self.args[0].startswith('/'):
request.path = self.args.pop(0).strip('/').split('/')
- def _parse_args(self, tags, props):
+ def _parse_args(self, props):
for arg in self.args:
arg = shlex.split(arg)
if not arg:
continue
- arg = arg[0]
- if '=' not in arg:
- tags.append(arg)
- continue
- arg, value = arg.split('=', 1)
+ if '=' in arg:
+ arg, value = arg[0].split('=', 1)
+ else:
+ arg = arg[0]
+ value = 1
arg = arg.strip()
enforce(arg, 'No argument name in %r expression', arg)
if arg in props:
diff --git a/sugar_network/client/routes.py b/sugar_network/client/routes.py
index 38f586e..942b052 100644
--- a/sugar_network/client/routes.py
+++ b/sugar_network/client/routes.py
@@ -508,14 +508,13 @@ class ClientRoutes(model.Routes, journal.Routes):
requires=request.get('requires'))
else:
pipe = injector.clone(request.guid)
+ event = {}
for event in pipe:
event['event'] = 'clone'
self.broadcast(event)
- for __ in clones.walk(request.guid):
- break
- else:
- # Cloning was failed
+ if event.get('state') == 'failure':
self._checkin_context(request.guid, {'clone': 0})
+ raise RuntimeError(event['error'])
def _clone_jobject(self, request, get_props):
if request.content:
diff --git a/sugar_network/db/directory.py b/sugar_network/db/directory.py
index b8d9176..b9fc02c 100644
--- a/sugar_network/db/directory.py
+++ b/sugar_network/db/directory.py
@@ -148,7 +148,7 @@ class Directory(object):
cached_props = self._index.get_cached(guid)
record = self._storage.get(guid)
enforce(cached_props or record.exists, http.NotFound,
- 'Document %r does not exist in %r',
+ 'Resource %r does not exist in %r',
guid, self.metadata.name)
return self.document_class(guid, record, cached_props)
diff --git a/sugar_network/db/resource.py b/sugar_network/db/resource.py
index fe071dc..7209c49 100644
--- a/sugar_network/db/resource.py
+++ b/sugar_network/db/resource.py
@@ -122,6 +122,9 @@ class Resource(object):
def modified(self, prop):
return prop in self._modifies
+ def __contains__(self, prop):
+ return self.get(prop)
+
def __getitem__(self, prop):
return self.get(prop)
diff --git a/sugar_network/db/routes.py b/sugar_network/db/routes.py
index e61199a..5706587 100644
--- a/sugar_network/db/routes.py
+++ b/sugar_network/db/routes.py
@@ -215,14 +215,13 @@ class Routes(object):
else access)
if value is None:
value = {'blob': None}
- elif isinstance(value, dict):
- enforce('url' in value,
- 'Key %r is not specified in %r blob property',
- 'url', name)
- value = {'url': value['url']}
- else:
+ elif isinstance(value, basestring) or hasattr(value, 'read'):
value = _read_blob(request, prop, value)
blobs.append(value['blob'])
+ elif isinstance(value, dict):
+ enforce('url' in value or 'blob' in value, 'No bundle')
+ else:
+ raise RuntimeError('Incorrect BLOB value')
else:
prop.assert_access(access)
if prop.localized and isinstance(value, basestring):
diff --git a/sugar_network/node/routes.py b/sugar_network/node/routes.py
index 3e457c8..e2781e7 100644
--- a/sugar_network/node/routes.py
+++ b/sugar_network/node/routes.py
@@ -139,14 +139,15 @@ class NodeRoutes(db.Routes, model.Routes):
return toolkit.iter_file(path)
@route('POST', ['implementation'], cmd='release',
- mime_type='application/json')
+ arguments={'initial': False},
+ mime_type='application/json', acl=ACL.AUTH | ACL.AUTHOR)
def release(self, request, document):
with toolkit.NamedTemporaryFile() as blob:
shutil.copyfileobj(request.content_stream, blob)
blob.flush()
- with load_bundle(self.volume, blob.name, request) as impl:
+ with load_bundle(self.volume, request, blob.name) as impl:
impl['data']['blob'] = blob.name
- return impl['guid']
+ return impl['guid']
@route('DELETE', [None, None], acl=ACL.AUTH | ACL.AUTHOR)
def delete(self, request):
@@ -405,12 +406,15 @@ class NodeRoutes(db.Routes, model.Routes):
@contextmanager
-def load_bundle(volume, bundle_path, impl=None):
- if impl is None:
- impl = {}
+def load_bundle(volume, request, bundle_path):
+ impl = request.copy()
+ initial = False
+ if 'initial' in impl:
+ initial = impl.pop('initial')
data = impl.setdefault('data', {})
data['blob'] = bundle_path
- context_updates = {}
+ contexts = volume['context']
+ context = None
try:
bundle = Bundle(bundle_path, mime_type='application/zip')
@@ -421,14 +425,16 @@ def load_bundle(volume, bundle_path, impl=None):
_logger.debug('Load Sugar Activity bundle from %r', bundle_path)
context_type = 'activity'
unpack_size = 0
+
with bundle:
for arcname in bundle.get_names():
unpack_size += bundle.getmember(arcname).size
spec = bundle.get_spec()
extract = bundle.rootdir
- context_updates = _load_context_metadata(bundle, spec)
+ context = _load_context_metadata(bundle, spec)
if 'requires' in impl:
spec.requires.update(parse_requires(impl.pop('requires')))
+
impl['context'] = spec['context']
impl['version'] = spec['version']
impl['stability'] = spec['stability']
@@ -441,11 +447,15 @@ def load_bundle(volume, bundle_path, impl=None):
data['unpack_size'] = unpack_size
data['mime_type'] = 'application/vnd.olpc-sugar'
+ if initial and not contexts.exists(impl['context']):
+ context['guid'] = impl['context']
+ context['type'] = 'activity'
+ request.call(method='POST', path=['context'], content=context)
+ context = None
+
enforce('context' in impl, 'Context is not specified')
enforce('version' in impl, 'Version is not specified')
- enforce(volume['context'].exists(impl['context']),
- http.BadRequest, 'No such activity')
- enforce(context_type in volume['context'].get(spec['context'])['type'],
+ enforce(context_type in contexts.get(spec['context'])['type'],
http.BadRequest, 'Inappropriate bundle type')
if impl.get('license') in (None, EMPTY_LICENSE):
existing, total = volume['implementation'].find(
@@ -459,13 +469,15 @@ def load_bundle(volume, bundle_path, impl=None):
existing, __ = volume['implementation'].find(
context=impl['context'], version=impl['version'],
not_layer='deleted')
+ impl['guid'] = \
+ request.call(method='POST', path=['implementation'], content=impl)
for i in existing:
layer = i['layer'] + ['deleted']
volume['implementation'].update(i.guid, {'layer': layer})
- impl['guid'] = volume['implementation'].create(impl)
- if context_updates:
- volume['context'].update(impl['context'], context_updates)
+ if context:
+ request.call(method='PUT', path=['context', impl['context']],
+ content=context)
def _load_context_metadata(bundle, spec):
diff --git a/sugar_network/toolkit/http.py b/sugar_network/toolkit/http.py
index 07505c7..215ec03 100644
--- a/sugar_network/toolkit/http.py
+++ b/sugar_network/toolkit/http.py
@@ -206,6 +206,9 @@ class Connection(object):
if response.status_code != 200:
if response.status_code == 401:
+ enforce(method not in ('PUT', 'POST') or
+ not hasattr(data, 'read'),
+ 'Cannot resend data after authentication')
enforce(self._get_profile is not None,
'Operation is not available in anonymous mode')
_logger.info('User is not registered on the server, '
diff --git a/sugar_network/toolkit/router.py b/sugar_network/toolkit/router.py
index 2331a9d..c67ec97 100644
--- a/sugar_network/toolkit/router.py
+++ b/sugar_network/toolkit/router.py
@@ -103,16 +103,17 @@ class Request(dict):
cmd = None
content = None
content_type = None
+ content_stream = None
content_length = 0
principal = None
- _if_modified_since = None
- _accept_language = None
+ subcall = lambda *args: enforce(False)
def __init__(self, environ=None, method=None, path=None, cmd=None,
- **kwargs):
+ content=None, **kwargs):
dict.__init__(self)
- self._pos = 0
self._dirty_query = False
+ self._if_modified_since = None
+ self._accept_language = None
if environ is None:
self.environ = {}
@@ -120,6 +121,7 @@ class Request(dict):
self.path = path
self.cmd = cmd
self.update(kwargs)
+ self.content = content
return
self.environ = environ
@@ -145,7 +147,7 @@ class Request(dict):
if query:
self.url += '?' + query
- content_length = self.environ.get('CONTENT_LENGTH')
+ content_length = environ.get('CONTENT_LENGTH')
if content_length is not None:
self.content_length = int(content_length)
@@ -154,6 +156,10 @@ class Request(dict):
if self.content_type == 'application/json':
self.content = json.load(environ['wsgi.input'])
+ stream = environ.get('wsgi.input')
+ if stream is not None:
+ self.content_stream = _ContentStream(stream, self.content_length)
+
def __setitem__(self, key, value):
self._dirty_query = True
if key == 'cmd':
@@ -181,10 +187,6 @@ class Request(dict):
return self.path[2]
@property
- def content_stream(self):
- return self.environ.get('wsgi.input')
-
- @property
def static_prefix(self):
http_host = self.environ.get('HTTP_HOST')
if http_host:
@@ -227,17 +229,6 @@ class Request(dict):
self._dirty_query = False
return self.environ.get('QUERY_STRING')
- def read(self, size=None):
- if self.content_stream is None:
- return ''
- rest = max(0, self.content_length - self._pos)
- size = rest if size is None else min(rest, size)
- result = self.content_stream.read(size)
- if not result:
- return ''
- self._pos += len(result)
- return result
-
def add(self, key, *values):
existing_value = self.get(key)
for value in values:
@@ -248,6 +239,15 @@ class Request(dict):
else:
existing_value = self[key] = [existing_value, value]
+ def call(self, request=None, response=None, **kwargs):
+ if request is None:
+ request = Request(**kwargs)
+ if response is None:
+ response = Response()
+ request.principal = self.principal
+ request.environ = self.environ
+ return self.subcall(request, response)
+
def __repr__(self):
return '<Request method=%s path=%r cmd=%s query=%r>' % \
(self.method, self.path, self.cmd, dict(self))
@@ -300,7 +300,7 @@ class Response(dict):
return result
def __repr__(self):
- items = ['%s=%r' % i for i in self.items()]
+ items = ['%s=%r' % i for i in self.items() + self.meta.items()]
return '<Response %s>' % ' '.join(items)
def __contains__(self, key):
@@ -375,47 +375,44 @@ class Router(object):
cls = cls.__base__
def call(self, request, response):
- result = None
- try:
- result = self._call(request, response)
-
- if isinstance(result, Blob):
- if 'url' in result:
- raise http.Redirect(result['url'])
-
- path = result['blob']
- enforce(isfile(path), 'No such file')
-
- mtime = result.get('mtime') or os.stat(path).st_mtime
- if request.if_modified_since and mtime and \
- mtime <= request.if_modified_since:
- raise http.NotModified()
- response.last_modified = mtime
-
- response.content_type = result.get('mime_type') or \
- 'application/octet-stream'
-
- filename = result.get('filename')
- if not filename:
- filename = _filename(result.get('name') or
- splitext(split(path)[-1])[0],
- response.content_type)
- response['Content-Disposition'] = \
- 'attachment; filename="%s"' % filename
-
- result = file(path, 'rb')
-
- if hasattr(result, 'read'):
- if hasattr(result, 'fileno'):
- response.content_length = os.fstat(result.fileno()).st_size
- elif hasattr(result, 'seek'):
- result.seek(0, 2)
- response.content_length = result.tell()
- result.seek(0)
- result = _stream_reader(result)
- finally:
- _logger.trace('%s call: request=%s response=%r result=%r',
- self, request.environ, response, result)
+ request.subcall = self.call
+ result = self._call(request, response)
+
+ if isinstance(result, Blob):
+ if 'url' in result:
+ raise http.Redirect(result['url'])
+
+ path = result['blob']
+ enforce(isfile(path), 'No such file')
+
+ mtime = result.get('mtime') or os.stat(path).st_mtime
+ if request.if_modified_since and mtime and \
+ mtime <= request.if_modified_since:
+ raise http.NotModified()
+ response.last_modified = mtime
+
+ response.content_type = result.get('mime_type') or \
+ 'application/octet-stream'
+
+ filename = result.get('filename')
+ if not filename:
+ filename = _filename(result.get('name') or
+ splitext(split(path)[-1])[0],
+ response.content_type)
+ response['Content-Disposition'] = \
+ 'attachment; filename="%s"' % filename
+
+ result = file(path, 'rb')
+
+ if hasattr(result, 'read'):
+ if hasattr(result, 'fileno'):
+ response.content_length = os.fstat(result.fileno()).st_size
+ elif hasattr(result, 'seek'):
+ result.seek(0, 2)
+ response.content_length = result.tell()
+ result.seek(0)
+ result = _stream_reader(result)
+
return result
def __repr__(self):
@@ -478,11 +475,15 @@ class Router(object):
for key, value in response.meta.items():
response.set('X-SN-%s' % str(key), json.dumps(value))
+ if request.method == 'HEAD' and result is not None:
+ _logger.warning('Content from HEAD response is ignored')
+ result = None
+
+ _logger.trace('%s call: request=%s response=%r result=%r',
+ self, request.environ, response, result)
start_response(response.status, response.items())
- if request.method == 'HEAD':
- enforce(result is None, 'HEAD responses should not contain body')
- elif result_streamed:
+ if result_streamed:
for i in result:
yield i
elif result is not None:
@@ -594,6 +595,24 @@ class Router(object):
return valid
+class _ContentStream(object):
+
+ def __init__(self, stream, length):
+ self._stream = stream
+ self._length = length
+ self._pos = 0
+
+ def read(self, size=None):
+ if self._length:
+ the_rest = max(0, self._length - self._pos)
+ size = the_rest if size is None else min(the_rest, size)
+ result = self._stream.read(size)
+ if not result:
+ return ''
+ self._pos += len(result)
+ return result
+
+
def _filename(names, mime_type):
if type(names) not in (list, tuple):
names = [names]
diff --git a/tests/units/client/online_routes.py b/tests/units/client/online_routes.py
index 691de3e..891b239 100755
--- a/tests/units/client/online_routes.py
+++ b/tests/units/client/online_routes.py
@@ -342,12 +342,13 @@ class OnlineRoutes(tests.Test):
},
}})
- ipc.put(['context', context], 2, cmd='clone', nodeps=1, requires='dep4')
+ self.assertRaises(RuntimeError, ipc.put, ['context', context], 2, cmd='clone', nodeps=1, requires='foo')
coroutine.sleep(.1)
self.assertEqual({'clone': 0}, ipc.get(['context', context], reply=['clone']))
assert not exists('Activities/TestActivity/activity/activity.info')
ipc.put(['context', context], 2, cmd='clone', nodeps=1)
+ # XXX seems to be an ugly low level bug, removing the following sleep means not reasing HTTP response for the next request
coroutine.sleep(.1)
self.assertEqual({'clone': 2}, ipc.get(['context', context], reply=['clone']))
self.assertEqual('2', Spec('Activities/TestActivity/activity/activity.info')['version'])
diff --git a/tests/units/db/routes.py b/tests/units/db/routes.py
index f5d44db..1ea43ed 100755
--- a/tests/units/db/routes.py
+++ b/tests/units/db/routes.py
@@ -1605,8 +1605,8 @@ class RoutesTest(tests.Test):
request.cmd = cmd
request.content = content
request.content_type = content_type
- if request.content_stream is not None:
- request.content_length = len(request.content_stream.getvalue())
+ if content_stream is not None:
+ request.content_length = len(content_stream.getvalue())
request.update(kwargs)
request.principal = principal
router = Router(routes(self.volume))
diff --git a/tests/units/node/node.py b/tests/units/node/node.py
index 014ecfe..b56a61f 100755
--- a/tests/units/node/node.py
+++ b/tests/units/node/node.py
@@ -12,7 +12,7 @@ from os.path import exists
from __init__ import tests
-from sugar_network import db, node, model
+from sugar_network import db, node, model, client
from sugar_network.client import Connection
from sugar_network.toolkit import http, coroutine
from sugar_network.toolkit.rrd import Rrd
@@ -672,6 +672,9 @@ class NodeTest(tests.Test):
self.assertEqual('developer', impl['stability'])
self.assertEqual(['Public Domain'], impl['license'])
self.assertEqual('developer', impl['stability'])
+ assert impl['ctime'] > 0
+ assert impl['mtime'] > 0
+ self.assertEqual({tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, impl['author'])
data = impl.meta('data')
self.assertEqual({
@@ -751,7 +754,7 @@ class NodeTest(tests.Test):
self.assertEqual([], volume['implementation'].get(guid4)['layer'])
self.assertEqual(bundle3, conn.get(['context', 'bundle_id'], cmd='clone'))
- def test_release_LoadMetadata(self):
+ def test_release_UpdateContext(self):
volume = self.start_master()
conn = Connection()
@@ -820,6 +823,75 @@ class NodeTest(tests.Test):
self.assertEqual('http://wiki.sugarlabs.org/go/Activities/Image_Viewer', context['homepage'])
self.assertEqual(['image/bmp', 'image/gif'], context['mime_types'])
+ def test_release_CreateContext(self):
+ volume = self.start_master()
+ conn = Connection()
+
+ bundle = self.zips(
+ ('ImageViewer.activity/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'bundle_id = org.laptop.ImageViewerActivity',
+ 'name = Image Viewer',
+ 'summary = The Image Viewer activity is a simple and fast image viewer tool',
+ 'description = It has features one would expect of a standard image viewer, like zoom, rotate, etc.',
+ 'homepage = http://wiki.sugarlabs.org/go/Activities/Image_Viewer',
+ 'activity_version = 22',
+ 'license = GPLv2+',
+ 'icon = activity-imageviewer',
+ 'exec = true',
+ 'mime_types = image/bmp;image/gif',
+ ])),
+ ('ImageViewer.activity/activity/activity-imageviewer.svg', ''),
+ )
+ self.assertRaises(http.NotFound, conn.request, 'POST', ['implementation'], bundle, params={'cmd': 'release'})
+ impl = json.load(conn.request('POST', ['implementation'], bundle, params={'cmd': 'release', 'initial': 1}).raw)
+
+ context = volume['context'].get('org.laptop.ImageViewerActivity')
+ self.assertEqual({'en': 'Image Viewer'}, context['title'])
+ self.assertEqual({'en': 'The Image Viewer activity is a simple and fast image viewer tool'}, context['summary'])
+ self.assertEqual({'en': 'It has features one would expect of a standard image viewer, like zoom, rotate, etc.'}, context['description'])
+ self.assertEqual('http://wiki.sugarlabs.org/go/Activities/Image_Viewer', context['homepage'])
+ self.assertEqual(['image/bmp', 'image/gif'], context['mime_types'])
+ assert context['ctime'] > 0
+ assert context['mtime'] > 0
+ self.assertEqual({tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, context['author'])
+
+ def test_release_AuthorsOnly(self):
+ volume = self.start_master()
+ bundle = self.zips(
+ ('ImageViewer.activity/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'bundle_id = org.laptop.ImageViewerActivity',
+ 'name = Image Viewer',
+ 'activity_version = 1',
+ 'license = GPLv2+',
+ 'icon = activity-imageviewer',
+ 'exec = true',
+ ])),
+ ('ImageViewer.activity/activity/activity-imageviewer.svg', ''),
+ )
+
+ self.override(client, 'sugar_uid', lambda: tests.UID)
+ conn = Connection()
+ impl1 = json.load(conn.request('POST', ['implementation'], bundle, params={'cmd': 'release', 'initial': 1}).raw)
+ impl2 = json.load(conn.request('POST', ['implementation'], bundle, params={'cmd': 'release'}).raw)
+ self.assertEqual(['deleted'], volume['implementation'].get(impl1)['layer'])
+ self.assertEqual([], volume['implementation'].get(impl2)['layer'])
+
+ self.override(client, 'sugar_uid', lambda: tests.UID2)
+ self.override(client, 'sugar_profile', lambda: {
+ 'name': 'test',
+ 'color': '#000000,#000000',
+ 'machine_sn': '',
+ 'machine_uuid': '',
+ 'pubkey': tests.PUBKEY2,
+ })
+ conn = Connection()
+ conn.get(cmd='whoami')
+ self.assertRaises(http.Forbidden, conn.request, 'POST', ['implementation'], bundle, params={'cmd': 'release'})
+ self.assertEqual(['deleted'], volume['implementation'].get(impl1)['layer'])
+ self.assertEqual([], volume['implementation'].get(impl2)['layer'])
+
def call(routes, method, document=None, guid=None, prop=None, principal=None, cmd=None, content=None, **kwargs):
path = []
diff --git a/tests/units/toolkit/router.py b/tests/units/toolkit/router.py
index 32e26f3..b970a90 100755
--- a/tests/units/toolkit/router.py
+++ b/tests/units/toolkit/router.py
@@ -594,26 +594,40 @@ class RouterTest(tests.Test):
self.value = value
def read(self, size):
+ print self.pos, size, len(self.value)
assert self.pos + size <= len(self.value)
result = self.value[self.pos:self.pos + size]
self.pos += size
return result
- request = Request({'PATH_INFO': '/', 'REQUEST_METHOD': 'GET', 'wsgi.input': Stream('123')})
- request.content_length = len(request.content_stream.value)
- self.assertEqual('123', request.read())
- self.assertEqual('', request.read())
- self.assertEqual('', request.read(10))
-
- request = Request({'PATH_INFO': '/', 'REQUEST_METHOD': 'GET', 'wsgi.input': Stream('123')})
- request.content_length = len(request.content_stream.value)
- self.assertEqual('123', request.read(10))
-
- request = Request({'PATH_INFO': '/', 'REQUEST_METHOD': 'GET', 'wsgi.input': Stream('123')})
- request.content_length = len(request.content_stream.value)
- self.assertEqual('1', request.read(1))
- self.assertEqual('2', request.read(1))
- self.assertEqual('3', request.read())
+ request = Request({
+ 'PATH_INFO': '/',
+ 'REQUEST_METHOD': 'GET',
+ 'CONTENT_LENGTH': '3',
+ 'wsgi.input': Stream('123'),
+ })
+ self.assertEqual('123', request.content_stream.read())
+ self.assertEqual('', request.content_stream.read())
+ self.assertEqual('', request.content_stream.read(10))
+
+ request = Request({
+ 'PATH_INFO': '/',
+ 'REQUEST_METHOD': 'GET',
+ 'CONTENT_LENGTH': '3',
+ 'wsgi.input': Stream('123'),
+ })
+ self.assertEqual('123', request.content_stream.read(10))
+
+ request = Request({
+ 'PATH_INFO': '/',
+ 'REQUEST_METHOD': 'GET',
+ 'CONTENT_LENGTH': '3',
+ 'wsgi.input': Stream('123'),
+ })
+ self.assertEqual('1', request.content_stream.read(1))
+ self.assertEqual('2', request.content_stream.read(1))
+ self.assertEqual('3', request.content_stream.read())
+ self.assertEqual('', request.content_stream.read())
def test_IntArguments(self):