From 1eff810db0c62174e8d445f15e4b1604665ac18d Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Sat, 02 Nov 2013 12:16:48 +0000 Subject: Not only original authors can upload new implementations --- diff --git a/sugar_network/client/implementations.py b/sugar_network/client/implementations.py index 3cd59de..f78df33 100644 --- a/sugar_network/client/implementations.py +++ b/sugar_network/client/implementations.py @@ -261,11 +261,7 @@ class Routes(object): with file(data_path, 'wb') as f: shutil.copyfileobj(blob, f) impl = sel.copy() - impl['layer'] = [] - impl['ctime'] = impl['mtime'] = int(time.time()) - impl['author'] = {} - impl['notes'] = '' - impl['tags'] = [] + impl['mtime'] = impl['ctime'] impls.create(impl) return cache_call(guid, size) except Exception: @@ -320,7 +316,9 @@ class Routes(object): guid = basename(os.readlink(context.path('.clone'))) impl = self._volume['implementation'].get(guid) response.meta = impl.properties([ - 'guid', 'context', 'license', 'version', 'stability', 'data']) + 'guid', 'ctime', 'layer', 'author', 'tags', + 'context', 'version', 'stability', 'license', 'notes', 'data', + ]) return impl.meta('data') diff --git a/sugar_network/client/solver.py b/sugar_network/client/solver.py index 9f29e1d..0b24579 100644 --- a/sugar_network/client/solver.py +++ b/sugar_network/client/solver.py @@ -194,7 +194,7 @@ def _load_feed(context): feed_content = None try: feed_content = _call(method='GET', path=['context', context], - cmd='feed', stability=_stability, + cmd='feed', layer='origin', stability=_stability, distro=lsb_release.distributor_id()) _logger.trace('[%s] Found feed: %r', context, feed_content) except http.ServiceUnavailable: diff --git a/sugar_network/db/directory.py b/sugar_network/db/directory.py index 86afed2..6bb2d70 100644 --- a/sugar_network/db/directory.py +++ b/sugar_network/db/directory.py @@ -225,6 +225,25 @@ class Directory(object): self.commit() self.checkpoint() + def patch(self, guid, props, accept_language=None): + if not accept_language: + accept_language = toolkit.default_lang() + orig = self.get(guid) + patch = {} + for prop, value in (props or {}).items(): + if orig[prop] == value: + continue + if isinstance(self.metadata[prop], StoredProperty) and \ + self.metadata[prop].localized: + if isinstance(value, dict): + orig_value = dict([(i, orig[prop].get(i)) for i in value]) + if orig_value == value: + continue + elif orig.get(prop, accept_language) == value: + continue + patch[prop] = value + return patch + def diff(self, seq, exclude_seq=None, **params): if exclude_seq is None: exclude_seq = [] diff --git a/sugar_network/model/implementation.py b/sugar_network/model/implementation.py index f1c1c23..afeda82 100644 --- a/sugar_network/model/implementation.py +++ b/sugar_network/model/implementation.py @@ -19,7 +19,6 @@ from sugar_network import db, model from sugar_network.toolkit.router import ACL from sugar_network.toolkit.licenses import GOOD_LICENSES from sugar_network.toolkit.spec import parse_version -from sugar_network.toolkit import http, enforce class Implementation(db.Resource): @@ -31,10 +30,10 @@ class Implementation(db.Resource): @context.setter def context(self, value): - authors = self.volume['context'].get(value)['author'] - enforce(not self.request.principal and not authors or - self.request.principal in authors, http.Forbidden, - 'Only Context authors can submit new Implementations') + if self.request.principal: + authors = self.volume['context'].get(value)['author'] + if self.request.principal in authors: + self['layer'] = ('origin',) + tuple(self.layer) return value @db.indexed_property(prefix='L', full_text=True, typecast=[GOOD_LICENSES], diff --git a/sugar_network/model/routes.py b/sugar_network/model/routes.py index 17da118..6abb758 100644 --- a/sugar_network/model/routes.py +++ b/sugar_network/model/routes.py @@ -37,8 +37,10 @@ class VolumeRoutes(db.Routes): impls, __ = implementations.find(context=context.guid, not_layer='deleted', **request) for impl in impls: - version = impl.properties( - ['guid', 'version', 'stability', 'license']) + version = impl.properties([ + 'guid', 'ctime', 'layer', 'author', 'tags', + 'version', 'stability', 'license', 'notes', + ]) if context['dependencies']: requires = version.setdefault('requires', {}) for i in context['dependencies']: diff --git a/sugar_network/node/routes.py b/sugar_network/node/routes.py index 32ba497..80caba4 100644 --- a/sugar_network/node/routes.py +++ b/sugar_network/node/routes.py @@ -159,7 +159,7 @@ class NodeRoutes(model.VolumeRoutes, model.FrontRoutes): @route('POST', ['implementation'], cmd='submit', arguments={'initial': False}, - mime_type='application/json', acl=ACL.AUTH | ACL.AUTHOR) + mime_type='application/json', acl=ACL.AUTH) def submit_implementation(self, request, document): with toolkit.NamedTemporaryFile() as blob: shutil.copyfileobj(request.content_stream, blob) @@ -400,8 +400,10 @@ class NodeRoutes(model.VolumeRoutes, model.FrontRoutes): result = request.call(method=request.method, path=['implementation', impl['guid'], 'data'], response=response) - response.meta = impl.properties( - ['guid', 'context', 'license', 'version', 'stability']) + response.meta = impl.properties([ + 'guid', 'ctime', 'layer', 'author', 'tags', + 'context', 'version', 'stability', 'license', 'notes', + ]) response.meta['data'] = data = impl.meta('data') for key in ('mtime', 'seqno', 'blob'): if key in data: @@ -418,7 +420,9 @@ def load_bundle(volume, request, bundle_path): data = impl.setdefault('data', {}) data['blob'] = bundle_path contexts = volume['context'] - context = None + context = impl.get('context') + context_meta = None + impls = volume['implementation'] try: bundle = Bundle(bundle_path, mime_type='application/zip') @@ -434,11 +438,11 @@ def load_bundle(volume, request, bundle_path): for arcname in bundle.get_names(): unpack_size += bundle.getmember(arcname).size spec = bundle.get_spec() - context = _load_context_metadata(bundle, spec) + context_meta = _load_context_metadata(bundle, spec) if 'requires' in impl: spec.requires.update(parse_requires(impl.pop('requires'))) - impl['context'] = spec['context'] + context = impl['context'] = spec['context'] impl['version'] = spec['version'] impl['stability'] = spec['stability'] impl['license'] = spec['license'] @@ -449,37 +453,35 @@ def load_bundle(volume, request, bundle_path): 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 + if initial and not contexts.exists(context): + context_meta['guid'] = context + context_meta['type'] = 'activity' + request.call(method='POST', path=['context'], content=context_meta) + context_meta = None - enforce('context' in impl, 'Context is not specified') + enforce(context, 'Context is not specified') enforce('version' in impl, 'Version is not specified') - enforce(context_type in contexts.get(impl['context'])['type'], + enforce(context_type in contexts.get(context)['type'], http.BadRequest, 'Inappropriate bundle type') if impl.get('license') in (None, EMPTY_LICENSE): - existing, total = volume['implementation'].find( - context=impl['context'], order_by='-version', - not_layer='deleted') + existing, total = impls.find( + context=context, order_by='-version', not_layer='deleted') enforce(total, 'License is not specified') impl['license'] = next(existing)['license'] yield impl - existing, __ = volume['implementation'].find( - context=impl['context'], version=impl['version'], - not_layer='deleted') + existing, __ = impls.find( + context=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}) + impls.update(i.guid, {'layer': layer}) - if context: - request.call(method='PUT', path=['context', impl['context']], - content=context) + patch = contexts.patch(context, context_meta) + if patch and 'origin' in impls.get(impl['guid']).layer: + request.call(method='PUT', path=['context', context], content=patch) def _load_context_metadata(bundle, spec): diff --git a/tests/data/node/implementation/im/implementation/layer b/tests/data/node/implementation/im/implementation/layer index ed16e06..1fe70e4 100644 --- a/tests/data/node/implementation/im/implementation/layer +++ b/tests/data/node/implementation/im/implementation/layer @@ -1 +1 @@ -{"seqno": 3, "value": ["public"]} \ No newline at end of file +{"seqno": 3, "value": ["origin"]} \ No newline at end of file diff --git a/tests/data/node/implementation/im/implementation2/layer b/tests/data/node/implementation/im/implementation2/layer index ed16e06..1fe70e4 100644 --- a/tests/data/node/implementation/im/implementation2/layer +++ b/tests/data/node/implementation/im/implementation2/layer @@ -1 +1 @@ -{"seqno": 3, "value": ["public"]} \ No newline at end of file +{"seqno": 3, "value": ["origin"]} \ No newline at end of file diff --git a/tests/units/client/implementations.py b/tests/units/client/implementations.py index e63d132..d8173e9 100755 --- a/tests/units/client/implementations.py +++ b/tests/units/client/implementations.py @@ -154,6 +154,11 @@ class Implementations(tests.Test): 'context': 'bundle_id', 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), 'guid': impl, + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'unpack_size': len(activity_info), 'blob_size': len(blob), @@ -394,8 +399,8 @@ class Implementations(tests.Test): assert doc.meta('ctime') is not None assert doc.meta('mtime') is not None assert doc.meta('seqno') is not None - self.assertEqual({}, doc.meta('author')['value']) - self.assertEqual([], doc.meta('layer')['value']) + self.assertEqual({tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, doc.meta('author')['value']) + self.assertEqual(['origin'], doc.meta('layer')['value']) self.assertEqual('bundle_id', doc.meta('context')['value']) self.assertEqual(['Public Domain'], doc.meta('license')['value']) self.assertEqual('1', doc.meta('version')['value']) diff --git a/tests/units/client/offline_routes.py b/tests/units/client/offline_routes.py index 673f6b2..ac7af1b 100755 --- a/tests/units/client/offline_routes.py +++ b/tests/units/client/offline_routes.py @@ -104,6 +104,11 @@ class OfflineRoutes(tests.Test): 'stability': 'stable', 'guid': impl1, 'license': ['GPLv3+'], + 'layer': ['local'], + 'author': {}, + 'ctime': self.home_volume['implementation'].get(impl1).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {}}}, }, { @@ -111,6 +116,11 @@ class OfflineRoutes(tests.Test): 'stability': 'stable', 'guid': impl2, 'license': ['GPLv3+'], + 'layer': ['local'], + 'author': {}, + 'ctime': self.home_volume['implementation'].get(impl2).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'spec': {'*-*': { 'requires': { @@ -293,6 +303,11 @@ class OfflineRoutes(tests.Test): 'stability': 'stable', 'version': '1', 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'unpack_size': len(activity_info), 'blob_size': len(blob), @@ -354,6 +369,7 @@ Can't find all required implementations: 'license': 'GPLv3+', 'version': '1', 'stability': 'stable', + 'layer': ['origin'], }) self.home_volume['implementation'].update(impl, {'data': { 'spec': { @@ -394,6 +410,7 @@ Can't find all required implementations: 'license': 'GPLv3+', 'version': '1', 'stability': 'stable', + 'layer': ['origin'], }) self.home_volume['implementation'].update(impl, {'data': { 'spec': { @@ -431,6 +448,11 @@ Can't find all required implementations: 'license': ['GPLv3+'], 'stability': 'stable', 'version': '1', + 'layer': ['origin', 'local'], + 'author': {}, + 'ctime': self.home_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {'dep': {}}}}, }, diff --git a/tests/units/client/online_routes.py b/tests/units/client/online_routes.py index 46c18c1..ee2efe8 100755 --- a/tests/units/client/online_routes.py +++ b/tests/units/client/online_routes.py @@ -187,6 +187,11 @@ class OnlineRoutes(tests.Test): 'stability': 'stable', 'guid': impl1, 'license': ['GPLv3+'], + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl1).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {}}}, }, { @@ -194,6 +199,11 @@ class OnlineRoutes(tests.Test): 'stability': 'stable', 'guid': impl2, 'license': ['GPLv3+'], + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl2).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'spec': {'*-*': { 'requires': { @@ -440,6 +450,11 @@ Can't find all required implementations: 'stability': 'stable', 'version': '1', 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'spec': { '*-*': { @@ -491,6 +506,11 @@ Can't find all required implementations: 'license': ['GPLv3+'], 'version': '1', 'stability': 'stable', + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'foo': 'bar', 'blob_size': len(blob), @@ -638,6 +658,11 @@ Can't find all required implementations: 'license': ['Public Domain'], 'stability': 'stable', 'version': '1', + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'unpack_size': len(activity_info), 'blob_size': len(blob), @@ -846,6 +871,11 @@ Can't find all required implementations: 'stability': 'stable', 'version': '1', 'context': 'bundle_id', + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'blob_size': len(blob), 'mime_type': 'application/vnd.olpc-sugar', @@ -866,6 +896,11 @@ Can't find all required implementations: 'stability': 'stable', 'version': '1', 'context': 'bundle_id', + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'blob': blob_path, 'blob_size': len(blob), @@ -901,6 +936,11 @@ Can't find all required implementations: 'license': ['Public Domain'], 'stability': 'stable', 'version': '1', + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'blob_size': len(blob), 'unpack_size': len(activity_info), @@ -942,6 +982,11 @@ Can't find all required implementations: 'stability': 'stable', 'version': '2', 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'blob_size': len(blob), 'unpack_size': len(activity_info), @@ -1039,6 +1084,11 @@ Can't find all required implementations: 'stability': 'stable', 'version': '1', 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'blob_size': len(blob), 'unpack_size': len(activity_info), @@ -1176,13 +1226,13 @@ Can't find all required implementations: ipc.get(['context'], reply=['guid', 'layer'], layer='public')['result']) self.assertEqual( - [{'guid': impl, 'layer': ['public']}], + [{'guid': impl, 'layer': ['origin', 'public']}], ipc.get(['implementation'], reply=['guid', 'layer'])['result']) self.assertEqual( [], ipc.get(['implementation'], reply=['guid', 'layer'], layer='foo')['result']) self.assertEqual( - [{'guid': impl, 'layer': ['public']}], + [{'guid': impl, 'layer': ['origin', 'public']}], ipc.get(['implementation'], reply=['guid', 'layer'], layer='public')['result']) self.assertEqual({ @@ -1191,6 +1241,11 @@ Can't find all required implementations: 'guid': impl, 'version': '1', 'license': ['GPLv3+'], + 'layer': ['origin', 'public'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {}}}, }], }, @@ -1205,6 +1260,11 @@ Can't find all required implementations: 'guid': impl, 'version': '1', 'license': ['GPLv3+'], + 'layer': ['origin', 'public'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {}}}, }], }, @@ -1229,7 +1289,7 @@ Can't find all required implementations: [], ipc.get(['implementation'], reply=['guid', 'layer'], layer='foo')['result']) self.assertEqual( - [{'guid': impl, 'layer': ['public']}], + [{'guid': impl, 'layer': ['origin', 'public']}], ipc.get(['implementation'], reply=['guid', 'layer'], layer='public')['result']) self.assertEqual({ @@ -1246,6 +1306,11 @@ Can't find all required implementations: 'guid': impl, 'version': '1', 'license': ['GPLv3+'], + 'layer': ['origin', 'public'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {}}}, }], }, diff --git a/tests/units/client/solver.py b/tests/units/client/solver.py index 88ee931..ac2ef52 100755 --- a/tests/units/client/solver.py +++ b/tests/units/client/solver.py @@ -107,6 +107,11 @@ class SolverTest(tests.Test): {'version': '1', 'guid': 'dep2', 'context': 'dep2', 'stability': 'packaged', 'license': None}, {'version': '1', 'guid': 'dep3', 'context': 'dep3', 'stability': 'packaged', 'license': None}, {'version': '1', 'context': context, 'guid': impl, 'stability': 'stable', 'license': ['GPLv3+'], + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {'commands': {'activity': {'exec': 'echo'}}, 'requires': {'dep2': {'restrictions': [['1', '2']]}, 'dep3': {}}}}}, 'requires': {'dep1': {}, 'dep2': {}}}, @@ -158,7 +163,17 @@ class SolverTest(tests.Test): }, }}) self.assertEqual([ - {'version': '1', 'context': context, 'guid': impl, 'stability': 'stable', 'license': ['GPLv3+'], + { + 'version': '1', + 'context': context, + 'guid': impl, + 'stability': 'stable', + 'license': ['GPLv3+'], + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {'commands': {'activity': {'exec': 'echo'}}, 'requires': {'sugar': {}}}}}}, {'version': '0.94', 'context': 'sugar', 'guid': 'sugar-0.94', 'stability': 'packaged', 'license': None}, ], @@ -179,7 +194,17 @@ class SolverTest(tests.Test): }, }}) self.assertEqual([ - {'version': '1', 'context': context, 'guid': impl, 'stability': 'stable', 'license': ['GPLv3+'], + { + 'version': '1', + 'context': context, + 'guid': impl, + 'stability': 'stable', + 'license': ['GPLv3+'], + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {'commands': {'activity': {'exec': 'echo'}}, 'requires': {'sugar': {'restrictions': [['0.80', '0.87']]}}}}}}, {'version': '0.86', 'context': 'sugar', 'guid': 'sugar-0.86', 'stability': 'packaged', 'license': None}, @@ -231,7 +256,17 @@ class SolverTest(tests.Test): }, }}) self.assertEqual([ - {'version': '1', 'context': context, 'guid': impl, 'stability': 'stable', 'license': ['GPLv3+'], + { + 'version': '1', + 'context': context, + 'guid': impl, + 'stability': 'stable', + 'license': ['GPLv3+'], + 'layer': ['origin'], + 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': {'spec': {'*-*': {'commands': {'activity': {'exec': 'echo'}}, 'requires': {'sugar': {}}}}}}, {'version': '0.94', 'context': 'sugar', 'guid': 'sugar-0.94', 'stability': 'packaged', 'license': None}, ], diff --git a/tests/units/db/resource.py b/tests/units/db/resource.py index f8a56b1..97e8b1b 100755 --- a/tests/units/db/resource.py +++ b/tests/units/db/resource.py @@ -23,7 +23,7 @@ from sugar_network.db import directory as directory_ from sugar_network.db.directory import Directory from sugar_network.db.index import IndexWriter from sugar_network.toolkit.router import ACL -from sugar_network.toolkit import Sequence +from sugar_network.toolkit import http, Sequence class ResourceTest(tests.Test): @@ -332,6 +332,45 @@ class ResourceTest(tests.Test): json.load(file('%s/%s/prop' % (guid_1[:2], guid_1)))['seqno'], seqno) + def test_patch(self): + + class Document(db.Resource): + + @db.indexed_property(slot=1) + def prop1(self, value): + return value + + @db.indexed_property(slot=2) + def prop2(self, value): + return value + + directory = Directory(tests.tmpdir, Document, IndexWriter) + + self.assertRaises(http.NotFound, directory.patch, 'absent', {}) + + directory.create({'guid': '1', 'prop1': '1', 'prop2': '2'}) + self.assertEqual({}, directory.patch('1', {})) + self.assertEqual({}, directory.patch('1', {'prop1': '1', 'prop2': '2'})) + self.assertEqual({'prop1': '1_'}, directory.patch('1', {'prop1': '1_', 'prop2': '2'})) + self.assertEqual({'prop1': '1_', 'prop2': '2_'}, directory.patch('1', {'prop1': '1_', 'prop2': '2_'})) + + def test_patch_LocalizedProps(self): + + class Document(db.Resource): + + @db.indexed_property(slot=1, localized=True) + def prop(self, value): + return value + + directory = Directory(tests.tmpdir, Document, IndexWriter) + + directory.create({'guid': '1', 'prop': {'ru': 'ru'}}) + self.assertEqual({}, directory.patch('1', {'prop': 'ru'})) + self.assertEqual({'prop': {'ru': 'ru_'}}, directory.patch('1', {'prop': {'ru': 'ru_'}})) + self.assertEqual({'prop': {'en': 'en'}}, directory.patch('1', {'prop': {'en': 'en'}})) + self.assertEqual({'prop': {'ru': 'ru', 'en': 'en'}}, directory.patch('1', {'prop': {'ru': 'ru', 'en': 'en'}})) + self.assertEqual({'prop': {'ru': 'ru_', 'en': 'en'}}, directory.patch('1', {'prop': {'ru': 'ru_', 'en': 'en'}})) + def test_diff(self): class Document(db.Resource): diff --git a/tests/units/model/implementation.py b/tests/units/model/implementation.py index ea238c7..6b4bbc3 100755 --- a/tests/units/model/implementation.py +++ b/tests/units/model/implementation.py @@ -64,7 +64,7 @@ class ImplementationTest(tests.Test): xapian.sortable_serialise(eval('1''0000''0000''6''001')), _fmt_version('1-r1.2-3')) - def test_WrongAuthor(self): + def test_OriginalAuthor(self): self.start_online_client() client = IPCConnection() @@ -77,18 +77,47 @@ class ImplementationTest(tests.Test): 'author': {'fake': None}, }) - impl = {'context': 'context', - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - } - self.assertRaises(http.Forbidden, client.post, ['implementation'], impl) - self.assertEqual(0, self.node_volume['implementation'].find()[1]) + guid = client.post(['implementation'], { + 'context': 'context', + 'license': 'GPLv3+', + 'version': '1', + 'stability': 'stable', + 'notes': '', + }) + self.assertEqual([], self.node_volume['implementation'].get(guid)['layer']) + + guid = client.post(['implementation'], { + 'context': 'context', + 'license': 'GPLv3+', + 'version': '1', + 'stability': 'stable', + 'notes': '', + 'layer': ['foo'], + }) + self.assertEqual(['foo'], self.node_volume['implementation'].get(guid)['layer']) self.node_volume['context'].update('context', {'author': {tests.UID: None}}) - guid = client.post(['implementation'], impl) - assert self.node_volume['implementation'].exists(guid) + + guid = client.post(['implementation'], { + 'context': 'context', + 'license': 'GPLv3+', + 'version': '1', + 'stability': 'stable', + 'notes': '', + }) + self.assertEqual(['origin'], self.node_volume['implementation'].get(guid)['layer']) + + guid = client.post(['implementation'], { + 'context': 'context', + 'license': 'GPLv3+', + 'version': '1', + 'stability': 'stable', + 'notes': '', + 'layer': ['foo'], + }) + self.assertEqual( + sorted(['foo', 'origin']), + sorted(self.node_volume['implementation'].get(guid)['layer'])) if __name__ == '__main__': diff --git a/tests/units/node/node.py b/tests/units/node/node.py index 4f090fc..e019df4 100755 --- a/tests/units/node/node.py +++ b/tests/units/node/node.py @@ -646,6 +646,11 @@ class NodeTest(tests.Test): 'guid': impl3, 'version': '3', 'license': ['GPLv3+'], + 'layer': ['origin'], + 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, + 'ctime': self.node_volume['implementation'].get(impl3).ctime, + 'notes': {'en-us': ''}, + 'tags': [], 'data': { 'blob_size': len(blob3), 'spec': { @@ -724,9 +729,9 @@ class NodeTest(tests.Test): guid2 = json.load(conn.request('POST', ['implementation'], bundle2, params={'cmd': 'submit'}).raw) self.assertEqual('1', volume['implementation'].get(guid1)['version']) - self.assertEqual([], volume['implementation'].get(guid1)['layer']) + self.assertEqual(['origin'], volume['implementation'].get(guid1)['layer']) self.assertEqual('2', volume['implementation'].get(guid2)['version']) - self.assertEqual([], volume['implementation'].get(guid2)['layer']) + self.assertEqual(['origin'], volume['implementation'].get(guid2)['layer']) self.assertEqual(bundle2, conn.get(['context', 'bundle_id'], cmd='clone')) activity_info = '\n'.join([ @@ -743,11 +748,11 @@ class NodeTest(tests.Test): guid3 = json.load(conn.request('POST', ['implementation'], bundle3, params={'cmd': 'submit'}).raw) self.assertEqual('1', volume['implementation'].get(guid1)['version']) - self.assertEqual(['deleted'], volume['implementation'].get(guid1)['layer']) + self.assertEqual(sorted(['origin', 'deleted']), sorted(volume['implementation'].get(guid1)['layer'])) self.assertEqual('2', volume['implementation'].get(guid2)['version']) - self.assertEqual([], volume['implementation'].get(guid2)['layer']) + self.assertEqual(['origin'], volume['implementation'].get(guid2)['layer']) self.assertEqual('1', volume['implementation'].get(guid3)['version']) - self.assertEqual([], volume['implementation'].get(guid3)['layer']) + self.assertEqual(['origin'], volume['implementation'].get(guid3)['layer']) self.assertEqual(bundle2, conn.get(['context', 'bundle_id'], cmd='clone')) activity_info = '\n'.join([ @@ -764,13 +769,13 @@ class NodeTest(tests.Test): guid4 = json.load(conn.request('POST', ['implementation'], bundle4, params={'cmd': 'submit'}).raw) self.assertEqual('1', volume['implementation'].get(guid1)['version']) - self.assertEqual(['deleted'], volume['implementation'].get(guid1)['layer']) + self.assertEqual(sorted(['origin', 'deleted']), sorted(volume['implementation'].get(guid1)['layer'])) self.assertEqual('2', volume['implementation'].get(guid2)['version']) - self.assertEqual(['deleted'], volume['implementation'].get(guid2)['layer']) + self.assertEqual(sorted(['origin', 'deleted']), sorted(volume['implementation'].get(guid2)['layer'])) self.assertEqual('1', volume['implementation'].get(guid3)['version']) - self.assertEqual([], volume['implementation'].get(guid3)['layer']) + self.assertEqual(['origin'], volume['implementation'].get(guid3)['layer']) self.assertEqual('2', volume['implementation'].get(guid4)['version']) - self.assertEqual([], volume['implementation'].get(guid4)['layer']) + self.assertEqual(['origin'], volume['implementation'].get(guid4)['layer']) self.assertEqual(bundle3, conn.get(['context', 'bundle_id'], cmd='clone')) def test_release_UpdateContext(self): @@ -875,7 +880,7 @@ class NodeTest(tests.Test): assert context['mtime'] > 0 self.assertEqual({tests.UID: {'role': 3, 'name': 'f470db873b6a35903aca1f2492188e1c4b9ffc42', 'order': 0}}, context['author']) - def test_release_AuthorsOnly(self): + def test_release_ByNonAuthors(self): volume = self.start_master() bundle = self.zips( ('ImageViewer.activity/activity/activity.info', '\n'.join([ @@ -893,14 +898,15 @@ class NodeTest(tests.Test): conn = Connection(auth=http.SugarAuth(join(tests.root, 'data', tests.UID))) impl1 = json.load(conn.request('POST', ['implementation'], bundle, params={'cmd': 'submit', 'initial': 1}).raw) impl2 = json.load(conn.request('POST', ['implementation'], bundle, params={'cmd': 'submit'}).raw) - self.assertEqual(['deleted'], volume['implementation'].get(impl1)['layer']) - self.assertEqual([], volume['implementation'].get(impl2)['layer']) + self.assertEqual(sorted(['origin', 'deleted']), sorted(volume['implementation'].get(impl1)['layer'])) + self.assertEqual(['origin'], volume['implementation'].get(impl2)['layer']) conn = Connection(auth=http.SugarAuth(join(tests.root, 'data', tests.UID2))) conn.get(cmd='whoami') - self.assertRaises(http.Forbidden, conn.request, 'POST', ['implementation'], bundle, params={'cmd': 'submit'}) - self.assertEqual(['deleted'], volume['implementation'].get(impl1)['layer']) - self.assertEqual([], volume['implementation'].get(impl2)['layer']) + impl3 = json.load(conn.request('POST', ['implementation'], bundle, params={'cmd': 'submit'}).raw) + self.assertEqual(sorted(['origin', 'deleted']), sorted(volume['implementation'].get(impl1)['layer'])) + self.assertEqual(sorted(['origin', 'deleted']), sorted(volume['implementation'].get(impl2)['layer'])) + self.assertEqual([], volume['implementation'].get(impl3)['layer']) def call(routes, method, document=None, guid=None, prop=None, principal=None, content=None, **kwargs): -- cgit v0.9.1