diff options
Diffstat (limited to 'tests/units')
-rw-r--r-- | tests/units/client/__main__.py | 3 | ||||
-rwxr-xr-x | tests/units/client/injector.py | 12 | ||||
-rwxr-xr-x | tests/units/client/journal.py | 10 | ||||
-rwxr-xr-x | tests/units/client/offline_routes.py | 560 | ||||
-rwxr-xr-x | tests/units/client/online_routes.py | 1611 | ||||
-rwxr-xr-x | tests/units/client/routes.py | 1242 | ||||
-rwxr-xr-x | tests/units/client/server_routes.py | 226 | ||||
-rwxr-xr-x | tests/units/db/resource.py | 35 | ||||
-rwxr-xr-x | tests/units/db/routes.py | 93 | ||||
-rwxr-xr-x | tests/units/db/volume.py | 51 | ||||
-rwxr-xr-x | tests/units/model/context.py | 88 | ||||
-rwxr-xr-x | tests/units/model/model.py | 9 | ||||
-rwxr-xr-x | tests/units/model/post.py | 1 | ||||
-rwxr-xr-x | tests/units/model/routes.py | 3 | ||||
-rwxr-xr-x | tests/units/node/master.py | 8 | ||||
-rwxr-xr-x | tests/units/node/model.py | 2 | ||||
-rwxr-xr-x | tests/units/node/node.py | 83 | ||||
-rwxr-xr-x | tests/units/node/slave.py | 2 | ||||
-rwxr-xr-x | tests/units/toolkit/router.py | 1 | ||||
-rwxr-xr-x | tests/units/toolkit/toolkit.py | 6 |
20 files changed, 1243 insertions, 2803 deletions
diff --git a/tests/units/client/__main__.py b/tests/units/client/__main__.py index 5d48161..fcc26a6 100644 --- a/tests/units/client/__main__.py +++ b/tests/units/client/__main__.py @@ -4,9 +4,6 @@ from __init__ import tests from journal import * from routes import * -from offline_routes import * -from online_routes import * -from server_routes import * from injector import * from packagekit import * diff --git a/tests/units/client/injector.py b/tests/units/client/injector.py index 4b12fe2..ec88975 100755 --- a/tests/units/client/injector.py +++ b/tests/units/client/injector.py @@ -12,8 +12,9 @@ from os.path import exists, join, basename from __init__ import tests from sugar_network import db, client -from sugar_network.client import Connection, keyfile, api, packagekit, injector as injector_ +from sugar_network.client import Connection, keyfile, api, packagekit, injector as injector_, model from sugar_network.client.injector import _PreemptivePool, Injector +from sugar_network.toolkit.coroutine import this from sugar_network.toolkit import http, lsb_release @@ -398,7 +399,7 @@ class InjectorTest(tests.Test): activity_bundle = self.zips(('topdir/activity/activity.info', activity_info)) release = conn.upload(['context'], activity_bundle, cmd='submit', initial=True) - self.assertRaises(RuntimeError, injector._solve, 'context', 'stable') + self.assertRaises(http.ServiceUnavailable, injector._solve, 'context', 'stable') def test_solve_ReuseCachedSolution(self): volume = self.start_master() @@ -514,7 +515,7 @@ class InjectorTest(tests.Test): self.assertEqual([client.api.value, 'stable', 0], json.load(file('client/solutions/context'))[:-1]) os.unlink('client/solutions/context') - self.assertRaises(RuntimeError, injector._solve, 'context', 'stable') + self.assertRaises(http.ServiceUnavailable, injector._solve, 'context', 'stable') def test_download_SetExecPermissions(self): volume = self.start_master() @@ -575,6 +576,7 @@ class InjectorTest(tests.Test): ], [i for i in injector.checkin('context')]) + self.assertEqual(['checkin'], this.volume['context']['context']['pins']) self.assertEqual(activity_info, file(join('client', 'releases', release, 'activity', 'activity.info')).read()) self.assertEqual([client.api.value, 'stable', 0, { 'context': { @@ -626,6 +628,7 @@ class InjectorTest(tests.Test): }, json.load(file('client/checkins'))) self.assertEqual(0, injector._pool._du) + self.assertEqual(['checkin'], this.volume['context']['context']['pins']) assert injector.checkout('context') assert exists(join('client', 'releases', release)) @@ -633,6 +636,7 @@ class InjectorTest(tests.Test): }, json.load(file('client/checkins'))) self.assertEqual(len(activity_info), injector._pool._du) + self.assertEqual([], this.volume['context']['context']['pins']) for __ in injector.checkin('context'): pass @@ -642,6 +646,7 @@ class InjectorTest(tests.Test): }, json.load(file('client/checkins'))) self.assertEqual(0, injector._pool._du) + self.assertEqual(['checkin'], this.volume['context']['context']['pins']) assert injector.checkout('context') assert not injector.checkout('context') @@ -651,6 +656,7 @@ class InjectorTest(tests.Test): }, json.load(file('client/checkins'))) self.assertEqual(len(activity_info), injector._pool._du) + self.assertEqual([], this.volume['context']['context']['pins']) def test_checkin_Refresh(self): volume = self.start_master() diff --git a/tests/units/client/journal.py b/tests/units/client/journal.py index 30c67f8..bae636b 100755 --- a/tests/units/client/journal.py +++ b/tests/units/client/journal.py @@ -161,10 +161,14 @@ class JournalTest(tests.Test): request = Request() request.path = ['journal', 'guid1', 'preview'] response = Response() + blob = ds.journal_get_preview(request, response) self.assertEqual({ - 'mime_type': 'image/png', - 'blob': '.sugar/default/datastore/gu/guid1/metadata/preview', - }, ds.journal_get_preview(request, response)) + 'content-type': 'image/png', + }, + dict(blob)) + self.assertEqual( + '.sugar/default/datastore/gu/guid1/metadata/preview', + blob.path) self.assertEqual(None, response.content_type) diff --git a/tests/units/client/offline_routes.py b/tests/units/client/offline_routes.py deleted file mode 100755 index c8ac58c..0000000 --- a/tests/units/client/offline_routes.py +++ /dev/null @@ -1,560 +0,0 @@ -#!/usr/bin/env python -# sugar-lint: disable - -import json -import hashlib -from cStringIO import StringIO -from zipfile import ZipFile -from os.path import exists - -from __init__ import tests, src_root - -from sugar_network import client, model -from sugar_network.client import IPCConnection, releases, packagekit -from sugar_network.client.routes import ClientRoutes -from sugar_network.model.user import User -from sugar_network.model.report import Report -from sugar_network.toolkit.router import Router -from sugar_network.toolkit import coroutine, http, lsb_release - - -class OfflineRoutes(tests.Test): - - def setUp(self, fork_num=0): - tests.Test.setUp(self, fork_num) - self.override(releases, '_activity_id_new', lambda: 'activity_id') - - def test_whoami(self): - ipc = self.start_offline_client() - - self.assertEqual( - {'guid': tests.UID, 'roles': [], 'route': 'offline'}, - ipc.get(cmd='whoami')) - - def test_Events(self): - ipc = self.start_offline_client() - events = [] - - def read_events(): - for event in ipc.subscribe(event='!commit'): - events.append(event) - job = coroutine.spawn(read_events) - coroutine.dispatch() - - guid = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - ipc.put(['context', guid], { - 'title': 'title_2', - }) - ipc.delete(['context', guid]) - coroutine.sleep(.1) - job.kill() - - self.assertEqual([ - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, - ], - events) - - def test_Feeds(self): - ipc = self.start_offline_client() - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - impl1 = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - }) - self.home_volume['release'].update(impl1, {'data': { - 'spec': {'*-*': {}}, - }}) - impl2 = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '2', - 'stability': 'stable', - 'notes': '', - }) - self.home_volume['release'].update(impl2, {'data': { - 'spec': {'*-*': { - 'requires': { - 'dep1': {}, - 'dep2': {'restrictions': [['1', '2']]}, - 'dep3': {'restrictions': [[None, '2']]}, - 'dep4': {'restrictions': [['3', None]]}, - }, - }}, - }}) - - self.assertEqual({ - 'releases': [ - { - 'version': '1', - 'stability': 'stable', - 'guid': impl1, - 'license': ['GPLv3+'], - 'layer': ['local'], - 'author': {}, - 'ctime': self.home_volume['release'].get(impl1).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': {'spec': {'*-*': {}}}, - }, - { - 'version': '2', - 'stability': 'stable', - 'guid': impl2, - 'license': ['GPLv3+'], - 'layer': ['local'], - 'author': {}, - 'ctime': self.home_volume['release'].get(impl2).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'spec': {'*-*': { - 'requires': { - 'dep1': {}, - 'dep2': {'restrictions': [['1', '2']]}, - 'dep3': {'restrictions': [[None, '2']]}, - 'dep4': {'restrictions': [['3', None]]}, - }, - }}, - }, - }, - ], - }, - ipc.get(['context', context], cmd='feed')) - - def test_BLOBs(self): - ipc = self.start_offline_client() - - guid = ipc.post(['context'], { - 'type': 'package', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - blob = 'logo_blob' - ipc.request('PUT', ['context', guid, 'logo'], blob) - - self.assertEqual( - blob, - ipc.request('GET', ['context', guid, 'logo']).content) - self.assertEqual({ - 'logo': { - 'url': 'http://127.0.0.1:5555/context/%s/logo' % guid, - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'image/png', - }, - }, - ipc.get(['context', guid], reply=['logo'])) - self.assertEqual([{ - 'logo': { - 'url': 'http://127.0.0.1:5555/context/%s/logo' % guid, - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'image/png', - }, - }], - ipc.get(['context'], reply=['logo'])['result']) - - self.assertEqual( - file(src_root + '/sugar_network/static/httpdocs/images/package.png').read(), - ipc.request('GET', ['context', guid, 'icon']).content) - self.assertEqual({ - 'icon': { - 'url': 'http://127.0.0.1:5555/static/images/package.png', - 'mime_type': 'image/png', - }, - }, - ipc.get(['context', guid], reply=['icon'])) - self.assertEqual([{ - 'icon': { - 'url': 'http://127.0.0.1:5555/static/images/package.png', - 'mime_type': 'image/png', - }, - }], - ipc.get(['context'], reply=['icon'])['result']) - - def test_favorite(self): - ipc = self.start_offline_client() - events = [] - - def read_events(): - for event in ipc.subscribe(event='!commit'): - events.append(event) - coroutine.spawn(read_events) - coroutine.dispatch() - - context1 = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title1', - 'summary': 'summary', - 'description': 'description', - }) - context2 = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title2', - 'summary': 'summary', - 'description': 'description', - }) - - self.assertEqual( - sorted([]), - sorted(ipc.get(['context'], layer='favorite')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='local')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context1, 'layer': ['local']}, {'guid': context2, 'layer': ['local']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['local']}, ipc.get(['context', context1], reply='layer')) - self.assertEqual(['local'], ipc.get(['context', context1, 'layer'])) - self.assertEqual({'layer': ['local']}, ipc.get(['context', context2], reply='layer')) - self.assertEqual(['local'], ipc.get(['context', context2, 'layer'])) - - del events[:] - ipc.put(['context', context1], True, cmd='favorite') - coroutine.sleep(.1) - - self.assertEqual( - {'guid': context1, 'resource': 'context', 'event': 'update'}, - events[-1]) - self.assertEqual( - sorted([{'guid': context1}]), - sorted(ipc.get(['context'], layer='favorite')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='local')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context1, 'layer': ['favorite', 'local']}, {'guid': context2, 'layer': ['local']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['favorite', 'local']}, ipc.get(['context', context1], reply='layer')) - self.assertEqual(['favorite', 'local'], ipc.get(['context', context1, 'layer'])) - self.assertEqual({'layer': ['local']}, ipc.get(['context', context2], reply='layer')) - self.assertEqual(['local'], ipc.get(['context', context2, 'layer'])) - - del events[:] - ipc.put(['context', context2], True, cmd='favorite') - coroutine.sleep(.1) - - self.assertEqual( - {'guid': context2, 'resource': 'context', 'event': 'update'}, - events[-1]) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='favorite')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='local')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context1, 'layer': ['favorite', 'local']}, {'guid': context2, 'layer': ['favorite', 'local']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['favorite', 'local']}, ipc.get(['context', context1], reply='layer')) - self.assertEqual(['favorite', 'local'], ipc.get(['context', context1, 'layer'])) - self.assertEqual({'layer': ['favorite', 'local']}, ipc.get(['context', context2], reply='layer')) - self.assertEqual(['favorite', 'local'], ipc.get(['context', context2, 'layer'])) - - del events[:] - ipc.put(['context', context1], False, cmd='favorite') - coroutine.sleep(.1) - - self.assertEqual( - {'guid': context1, 'resource': 'context', 'event': 'update'}, - events[-1]) - self.assertEqual( - sorted([{'guid': context2}]), - sorted(ipc.get(['context'], layer='favorite')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='local')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context1, 'layer': ['local']}, {'guid': context2, 'layer': ['favorite', 'local']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['local']}, ipc.get(['context', context1], reply='layer')) - self.assertEqual(['local'], ipc.get(['context', context1, 'layer'])) - self.assertEqual({'layer': ['favorite', 'local']}, ipc.get(['context', context2], reply='layer')) - self.assertEqual(['favorite', 'local'], ipc.get(['context', context2, 'layer'])) - - def test_launch_Activity(self): - local = self.start_online_client() - ipc = IPCConnection() - - activity_info = '\n'.join([ - '[Activity]', - 'name = TestActivity', - 'bundle_id = bundle_id', - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license=Public Domain', - ]) - blob = self.zips(['TestActivity/activity/activity.info', activity_info]) - impl = ipc.upload(['release'], StringIO(blob), cmd='submit', initial=True) - - ipc.put(['context', 'bundle_id'], True, cmd='clone') - solution = [{ - 'guid': impl, - 'context': 'bundle_id', - 'license': ['Public Domain'], - 'stability': 'stable', - 'version': '1', - 'path': tests.tmpdir + '/client/release/%s/%s/data.blob' % (impl[:2], impl), - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'unpack_size': len(activity_info), - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'application/vnd.olpc-sugar', - 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {}}}, - }, - }] - assert local['release'].exists(impl) - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/bu/bundle_id'))) - - self.node.stop() - coroutine.sleep(.1) - - log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id.log' - self.assertEqual([ - {'event': 'launch', 'foo': 'bar', 'activity_id': 'activity_id'}, - {'event': 'exec', 'activity_id': 'activity_id'}, - {'event': 'exit', 'activity_id': 'activity_id'}, - ], - [i for i in ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar')]) - assert local['release'].exists(impl) - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/bu/bundle_id'))) - - def test_ServiceUnavailableWhileSolving(self): - ipc = self.start_offline_client() - - self.assertEqual([ - {'event': 'failure', 'activity_id': 'activity_id', 'exception': 'ServiceUnavailable', 'error': "Resource 'foo' does not exist in 'context'"}, - ], - [i for i in ipc.get(['context', 'foo'], cmd='launch')]) - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - self.assertEqual([ - {'event': 'launch', 'activity_id': 'activity_id'}, - {'event': 'failure', 'activity_id': 'activity_id', 'exception': 'ServiceUnavailable', - 'stability': ['stable'], - 'logs': [ - tests.tmpdir + '/.sugar/default/logs/shell.log', - tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', - ], - 'error': """\ -Can't find all required implementations: -- %s -> (problem) - No known implementations at all""" % context}, - ], - [i for i in ipc.get(['context', context], cmd='launch')]) - - impl = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'layer': ['origin'], - }) - self.home_volume['release'].update(impl, {'data': { - 'spec': { - '*-*': { - 'commands': {'activity': {'exec': 'true'}}, - 'requires': {'dep': {}}, - }, - }, - }}) - self.assertEqual([ - {'event': 'launch', 'activity_id': 'activity_id'}, - {'event': 'failure', 'activity_id': 'activity_id', 'exception': 'ServiceUnavailable', - 'stability': ['stable'], - 'logs': [ - tests.tmpdir + '/.sugar/default/logs/shell.log', - tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', - ], - 'error': """\ -Can't find all required implementations: -- %s -> 1 (%s) -- dep -> (problem) - No known implementations at all""" % (context, impl)}, - ], - [i for i in ipc.get(['context', context], cmd='launch')]) - assert not exists('solutions/%s/%s' % (context[:2], context)) - - def test_ServiceUnavailableWhileInstalling(self): - ipc = self.start_offline_client() - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - impl = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'layer': ['origin'], - }) - self.home_volume['release'].update(impl, {'data': { - 'spec': { - '*-*': { - 'commands': {'activity': {'exec': 'true'}}, - 'requires': {'dep': {}}, - }, - }, - }}) - ipc.post(['context'], { - 'guid': 'dep', - 'type': 'package', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - 'aliases': { - lsb_release.distributor_id(): { - 'status': 'success', - 'binary': [['dep.bin']], - }, - }, - }) - - def resolve(names): - return dict([(i, {'name': i, 'pk_id': i, 'version': '0', 'arch': '*', 'installed': False}) for i in names]) - self.override(packagekit, 'resolve', resolve) - - self.assertEqual([ - {'event': 'launch', 'activity_id': 'activity_id'}, - {'event': 'failure', 'activity_id': 'activity_id', 'exception': 'ServiceUnavailable', 'error': 'Installation is not available in offline', - 'stability': ['stable'], - 'solution': [ - { 'guid': impl, - 'context': context, - 'license': ['GPLv3+'], - 'stability': 'stable', - 'version': '1', - 'layer': ['origin', 'local'], - 'author': {}, - 'ctime': self.home_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {'dep': {}}}}, - }, - }, - { 'guid': 'dep', - 'context': 'dep', - 'install': [{'arch': '*', 'installed': False, 'name': 'dep.bin', 'pk_id': 'dep.bin', 'version': '0'}], - 'license': None, - 'stability': 'packaged', - 'version': '0', - }, - ], - 'logs': [ - tests.tmpdir + '/.sugar/default/logs/shell.log', - tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', - ], - }, - ], - [i for i in ipc.get(['context', context], cmd='launch')]) - - def test_NoAuthors(self): - ipc = self.start_offline_client() - - guid = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - self.assertEqual( - {}, - self.home_volume['context'].get(guid)['author']) - self.assertEqual( - [], - ipc.get(['context', guid, 'author'])) - - def test_HandleDeletes(self): - ipc = self.start_offline_client() - - guid = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - - guid_path = 'db/context/%s/%s' % (guid[:2], guid) - assert exists(guid_path) - - ipc.delete(['context', guid]) - self.assertRaises(http.NotFound, ipc.get, ['context', guid]) - assert not exists(guid_path) - - def test_SubmitReport(self): - ipc = self.home_volume = self.start_offline_client() - - self.touch( - ['file1', 'content1'], - ['file2', 'content2'], - ['file3', 'content3'], - ) - events = [i for i in ipc.post(['report'], {'context': 'context', 'error': 'error', 'logs': [ - tests.tmpdir + '/file1', - tests.tmpdir + '/file2', - tests.tmpdir + '/file3', - ]}, cmd='submit')] - self.assertEqual('done', events[-1]['event']) - guid = events[-1]['guid'] - - self.assertEqual({ - 'context': 'context', - 'error': 'error', - }, - ipc.get(['report', guid], reply=['context', 'error'])) - zipfile = ZipFile('db/report/%s/%s/data.blob' % (guid[:2], guid)) - self.assertEqual('content1', zipfile.read('file1')) - self.assertEqual('content2', zipfile.read('file2')) - self.assertEqual('content3', zipfile.read('file3')) - - -if __name__ == '__main__': - tests.main() diff --git a/tests/units/client/online_routes.py b/tests/units/client/online_routes.py deleted file mode 100755 index 50df2ec..0000000 --- a/tests/units/client/online_routes.py +++ /dev/null @@ -1,1611 +0,0 @@ -#!/usr/bin/env python -# sugar-lint: disable - -import os -import json -import time -import copy -import shutil -import zipfile -import hashlib -from zipfile import ZipFile -from cStringIO import StringIO -from os.path import exists, lexists, basename - -from __init__ import tests, src_root - -from sugar_network import client, db, model -from sugar_network.client import IPCConnection, journal, routes, releases -from sugar_network.toolkit import coroutine, http -from sugar_network.toolkit.spec import Spec -from sugar_network.client.routes import ClientRoutes, Request, Response -from sugar_network.node.master import MasterRoutes -from sugar_network.db import Volume, Resource -from sugar_network.model.user import User -from sugar_network.model.report import Report -from sugar_network.model.context import Context -from sugar_network.model.release import Release -from sugar_network.model.post import Post -from sugar_network.toolkit.router import route -from sugar_network.toolkit import Option - -import requests - - -class OnlineRoutes(tests.Test): - - def setUp(self, fork_num=0): - tests.Test.setUp(self, fork_num) - self.override(releases, '_activity_id_new', lambda: 'activity_id') - - def test_whoami(self): - self.start_online_client() - ipc = IPCConnection() - - self.assertEqual( - {'guid': tests.UID, 'roles': [], 'route': 'proxy'}, - ipc.get(cmd='whoami')) - - def test_Events(self): - local_volume = self.start_online_client() - ipc = IPCConnection() - events = [] - - def read_events(): - for event in ipc.subscribe(event='!commit'): - events.append(event) - coroutine.spawn(read_events) - coroutine.dispatch() - - guid = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - ipc.put(['context', guid], { - 'title': 'title_2', - }) - coroutine.sleep(.1) - ipc.delete(['context', guid]) - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': tests.UID, 'resource': 'user', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, - ], - events) - del events[:] - - guid = self.node_volume['context'].create({ - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - self.node_volume['context'].update(guid, { - 'title': 'title_2', - }) - coroutine.sleep(.1) - self.node_volume['context'].delete(guid) - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, - ], - events) - del events[:] - - guid = local_volume['context'].create({ - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - local_volume['context'].update(guid, { - 'title': 'title_2', - }) - coroutine.sleep(.1) - local_volume['context'].delete(guid) - coroutine.sleep(.1) - - self.assertEqual([], events) - - self.node.stop() - coroutine.sleep(.1) - del events[:] - - guid = local_volume['context'].create({ - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - local_volume['context'].update(guid, { - 'title': 'title_2', - }) - coroutine.sleep(.1) - local_volume['context'].delete(guid) - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, - ], - events) - del events[:] - - def test_Feeds(self): - self.start_online_client() - ipc = IPCConnection() - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - impl1 = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['release'].update(impl1, {'data': { - 'spec': {'*-*': {}}, - }}) - impl2 = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '2', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['release'].update(impl2, {'data': { - 'spec': {'*-*': { - 'requires': { - 'dep1': {}, - 'dep2': {'restrictions': [['1', '2']]}, - 'dep3': {'restrictions': [[None, '2']]}, - 'dep4': {'restrictions': [['3', None]]}, - }, - }}, - 'blob_size': 1, - 'unpack_size': 2, - 'mime_type': 'foo', - }}) - - self.assertEqual({ - 'releases': [ - { - 'version': '1', - 'stability': 'stable', - 'guid': impl1, - 'license': ['GPLv3+'], - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl1).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': {'spec': {'*-*': {}}}, - }, - { - 'version': '2', - 'stability': 'stable', - 'guid': impl2, - 'license': ['GPLv3+'], - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl2).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'spec': {'*-*': { - 'requires': { - 'dep1': {}, - 'dep2': {'restrictions': [['1', '2']]}, - 'dep3': {'restrictions': [[None, '2']]}, - 'dep4': {'restrictions': [['3', None]]}, - }, - }}, - 'blob_size': 1, - 'unpack_size': 2, - 'mime_type': 'foo', - }, - }, - ], - }, - ipc.get(['context', context], cmd='feed')) - - def test_BLOBs(self): - self.start_online_client() - ipc = IPCConnection() - - guid = ipc.post(['context'], { - 'type': 'package', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - blob = 'logo_blob' - ipc.request('PUT', ['context', guid, 'logo'], blob, headers={'content-type': 'image/png'}) - - self.assertEqual( - blob, - ipc.request('GET', ['context', guid, 'logo']).content) - self.assertEqual({ - 'logo': { - 'url': 'http://127.0.0.1:8888/context/%s/logo' % guid, - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'image/png', - }, - }, - ipc.get(['context', guid], reply=['logo'])) - self.assertEqual([{ - 'logo': { - 'url': 'http://127.0.0.1:8888/context/%s/logo' % guid, - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'image/png', - }, - }], - ipc.get(['context'], reply=['logo'])['result']) - - self.assertEqual( - file(src_root + '/sugar_network/static/httpdocs/images/package.png').read(), - ipc.request('GET', ['context', guid, 'icon']).content) - self.assertEqual({ - 'icon': { - 'url': 'http://127.0.0.1:8888/static/images/package.png', - 'mime_type': 'image/png', - }, - }, - ipc.get(['context', guid], reply=['icon'])) - self.assertEqual([{ - 'icon': { - 'url': 'http://127.0.0.1:8888/static/images/package.png', - 'mime_type': 'image/png', - }, - }], - ipc.get(['context'], reply=['icon'])['result']) - - def test_favorite(self): - local = self.start_online_client() - ipc = IPCConnection() - events = [] - - def read_events(): - for event in ipc.subscribe(event='!commit'): - events.append(event) - coroutine.spawn(read_events) - coroutine.dispatch() - - context1 = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title1', - 'summary': 'summary', - 'description': 'description', - 'layer': ['foo'], - }) - context2 = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title2', - 'summary': 'summary', - 'description': 'description', - 'layer': ['foo'], - }) - - self.assertEqual( - sorted([]), - sorted(ipc.get(['context'], layer='favorite')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='foo')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context1, 'layer': ['foo']}, {'guid': context2, 'layer': ['foo']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['foo']}, ipc.get(['context', context1], reply='layer')) - self.assertEqual(['foo'], ipc.get(['context', context1, 'layer'])) - self.assertEqual({'layer': ['foo']}, ipc.get(['context', context2], reply='layer')) - self.assertEqual(['foo'], ipc.get(['context', context2, 'layer'])) - self.assertEqual( - sorted([]), - sorted([i['layer'] for i in local['context'].find(reply='layer')[0]])) - - del events[:] - ipc.put(['context', context1], True, cmd='favorite') - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': context1, 'resource': 'context', 'event': 'update'}, - ], - events) - self.assertEqual( - sorted([{'guid': context1}]), - sorted(ipc.get(['context'], layer='favorite')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='foo')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context1, 'layer': ['foo', 'favorite']}, {'guid': context2, 'layer': ['foo']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['foo', 'favorite']}, ipc.get(['context', context1], reply='layer')) - self.assertEqual(['foo', 'favorite'], ipc.get(['context', context1, 'layer'])) - self.assertEqual({'layer': ['foo']}, ipc.get(['context', context2], reply='layer')) - self.assertEqual(['foo'], ipc.get(['context', context2, 'layer'])) - self.assertEqual( - sorted([['foo', 'favorite']]), - sorted([i['layer'] for i in local['context'].find(reply='layer')[0]])) - - del events[:] - ipc.put(['context', context2], True, cmd='favorite') - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': context2, 'resource': 'context', 'event': 'update'}, - ], - events) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='favorite')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='foo')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context1, 'layer': ['foo', 'favorite']}, {'guid': context2, 'layer': ['foo', 'favorite']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['foo', 'favorite']}, ipc.get(['context', context1], reply='layer')) - self.assertEqual(['foo', 'favorite'], ipc.get(['context', context1, 'layer'])) - self.assertEqual({'layer': ['foo', 'favorite']}, ipc.get(['context', context2], reply='layer')) - self.assertEqual(['foo', 'favorite'], ipc.get(['context', context2, 'layer'])) - self.assertEqual( - sorted([(context1, ['foo', 'favorite']), (context2, ['foo', 'favorite'])]), - sorted([(i.guid, i['layer']) for i in local['context'].find(reply='layer')[0]])) - - del events[:] - ipc.put(['context', context1], False, cmd='favorite') - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': context1, 'resource': 'context', 'event': 'update'}, - ], - events) - self.assertEqual( - sorted([{'guid': context2}]), - sorted(ipc.get(['context'], layer='favorite')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'], layer='foo')['result'])) - self.assertEqual( - sorted([{'guid': context1}, {'guid': context2}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context1, 'layer': ['foo']}, {'guid': context2, 'layer': ['foo', 'favorite']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['foo']}, ipc.get(['context', context1], reply='layer')) - self.assertEqual(['foo'], ipc.get(['context', context1, 'layer'])) - self.assertEqual({'layer': ['foo', 'favorite']}, ipc.get(['context', context2], reply='layer')) - self.assertEqual(['foo', 'favorite'], ipc.get(['context', context2, 'layer'])) - self.assertEqual( - sorted([(context1, ['foo']), (context2, ['foo', 'favorite'])]), - sorted([(i.guid, i['layer']) for i in local['context'].find(reply='layer')[0]])) - - def test_clone_Fails(self): - self.start_online_client([User, Context, Release]) - conn = IPCConnection() - - self.assertEqual([ - {'event': 'failure', 'exception': 'NotFound', 'error': "Resource 'foo' does not exist in 'context'"}, - ], - [i for i in conn.put(['context', 'foo'], True, cmd='clone')]) - - context = conn.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - - self.assertEqual([ - {'event': 'failure', 'exception': 'NotFound', - 'stability': ['stable'], - 'logs': [ - tests.tmpdir + '/.sugar/default/logs/shell.log', - tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', - ], - 'error': """\ -Can't find all required implementations: -- %s -> (problem) - No known implementations at all""" % context}, - ], - [i for i in conn.put(['context', context], True, cmd='clone')]) - - assert not exists('solutions/%s/%s' % (context[:2], context)) - - impl = conn.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['release'].update(impl, {'data': { - 'blob_size': 1, - 'spec': { - '*-*': { - 'commands': { - 'activity': { - 'exec': 'echo', - }, - }, - }, - }, - }}) - - self.assertEqual([ - {'event': 'failure', 'exception': 'NotFound', 'error': 'BLOB does not exist', - 'stability': ['stable'], - 'logs': [ - tests.tmpdir + '/.sugar/default/logs/shell.log', - tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', - ], - 'solution': [{ - 'guid': impl, - 'context': context, - 'license': ['GPLv3+'], - 'stability': 'stable', - 'version': '1', - 'path': tests.tmpdir + '/client/release/%s/%s/data.blob' % (impl[:2], impl), - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'spec': { - '*-*': { - 'commands': { - 'activity': { - 'exec': 'echo', - }, - }, - }, - }, - 'blob_size': 1, - }, - }], - }, - ], - [i for i in conn.put(['context', context], True, cmd='clone')]) - assert not exists('solutions/%s/%s' % (context[:2], context)) - - def test_clone_Content(self): - local = self.start_online_client([User, Context, Release]) - ipc = IPCConnection() - events = [] - - def read_events(): - for event in ipc.subscribe(event='!commit'): - events.append(event) - coroutine.spawn(read_events) - coroutine.dispatch() - - context = ipc.post(['context'], { - 'type': 'book', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - impl = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - }) - blob = 'content' - self.node_volume['release'].update(impl, {'data': {'blob': StringIO(blob), 'foo': 'bar'}}) - clone_path = 'client/context/%s/%s/.clone' % (context[:2], context) - solution = [{ - 'guid': impl, - 'context': context, - 'license': ['GPLv3+'], - 'version': '1', - 'stability': 'stable', - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'foo': 'bar', - 'blob_size': len(blob), - }, - }] - - self.assertEqual([ - {'event': 'ready'}, - ], - [i for i in ipc.put(['context', context], True, cmd='clone')]) - - self.assertEqual({ - 'event': 'update', - 'guid': context, - 'resource': 'context', - }, - events[-1]) - self.assertEqual( - sorted([{'guid': context}]), - sorted(ipc.get(['context'], layer='clone')['result'])) - self.assertEqual( - sorted([{'guid': context}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': context, 'layer': ['clone']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['clone']}, ipc.get(['context', context], reply='layer')) - self.assertEqual(['clone'], ipc.get(['context', context, 'layer'])) - self.assertEqual( - [(context, ['clone'])], - [(i.guid, i['layer']) for i in local['context'].find(reply='layer')[0]]) - self.assertEqual({ - 'layer': ['clone'], - 'type': ['book'], - 'author': {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, - 'title': {'en-us': 'title'}, - }, - local['context'].get(context).properties(['layer', 'type', 'author', 'title'])) - self.assertEqual({ - 'context': context, - 'license': ['GPLv3+'], - 'version': '1', - 'stability': 'stable', - }, - local['release'].get(impl).properties(['context', 'license', 'version', 'stability'])) - blob_path = 'client/release/%s/%s/data.blob' % (impl[:2], impl) - solution[0]['path'] = tests.tmpdir + '/' + blob_path - self.assertEqual({ - 'seqno': 5, - 'blob_size': len(blob), - 'blob': tests.tmpdir + '/' + blob_path, - 'mtime': int(os.stat(blob_path[:-5]).st_mtime), - 'foo': 'bar', - }, - local['release'].get(impl).meta('data')) - self.assertEqual('content', file(blob_path).read()) - assert exists(clone_path + '/data.blob') - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/%s/%s' % (context[:2], context)))) - - self.assertEqual([ - ], - [i for i in ipc.put(['context', context], False, cmd='clone')]) - - self.assertEqual({ - 'event': 'update', - 'guid': context, - 'resource': 'context', - }, - events[-1]) - self.assertEqual( - sorted([{'guid': context, 'layer': []}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': []}, ipc.get(['context', context], reply='layer')) - self.assertEqual([], ipc.get(['context', context, 'layer'])) - self.assertEqual({ - 'layer': [], - 'type': ['book'], - 'author': {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, - 'title': {'en-us': 'title'}, - }, - local['context'].get(context).properties(['layer', 'type', 'author', 'title'])) - blob_path = 'client/release/%s/%s/data.blob' % (impl[:2], impl) - self.assertEqual({ - 'seqno': 5, - 'blob_size': len(blob), - 'blob': tests.tmpdir + '/' + blob_path, - 'mtime': int(os.stat(blob_path[:-5]).st_mtime), - 'foo': 'bar', - }, - local['release'].get(impl).meta('data')) - self.assertEqual('content', file(blob_path).read()) - assert not lexists(clone_path) - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/%s/%s' % (context[:2], context)))) - - self.assertEqual([ - {'event': 'ready'}, - ], - [i for i in ipc.put(['context', context], True, cmd='clone')]) - - self.assertEqual({ - 'event': 'update', - 'guid': context, - 'resource': 'context', - }, - events[-1]) - self.assertEqual( - sorted([{'guid': context, 'layer': ['clone']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - assert exists(clone_path + '/data.blob') - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/%s/%s' % (context[:2], context)))) - - def test_clone_Activity(self): - local = self.start_online_client([User, Context, Release]) - ipc = IPCConnection() - events = [] - - def read_events(): - for event in ipc.subscribe(event='!commit'): - events.append(event) - coroutine.spawn(read_events) - coroutine.dispatch() - - activity_info = '\n'.join([ - '[Activity]', - 'name = TestActivity', - 'bundle_id = bundle_id', - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license=Public Domain', - ]) - blob = self.zips(['TestActivity/activity/activity.info', activity_info]) - impl = ipc.upload(['release'], StringIO(blob), cmd='submit', initial=True) - clone_path = 'client/context/bu/bundle_id/.clone' - blob_path = tests.tmpdir + '/client/release/%s/%s/data.blob' % (impl[:2], impl) - solution = [{ - 'guid': impl, - 'context': 'bundle_id', - 'license': ['Public Domain'], - 'stability': 'stable', - 'version': '1', - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'unpack_size': len(activity_info), - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'application/vnd.olpc-sugar', - 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {}}}, - }, - }] - downloaded_solution = copy.deepcopy(solution) - downloaded_solution[0]['path'] = blob_path - - self.assertEqual([ - {'event': 'ready'}, - ], - [i for i in ipc.put(['context', 'bundle_id'], True, cmd='clone')]) - - self.assertEqual({ - 'event': 'update', - 'guid': 'bundle_id', - 'resource': 'context', - }, - events[-1]) - self.assertEqual( - sorted([{'guid': 'bundle_id'}]), - sorted(ipc.get(['context'], layer='clone')['result'])) - self.assertEqual( - sorted([{'guid': 'bundle_id'}]), - sorted(ipc.get(['context'])['result'])) - self.assertEqual( - sorted([{'guid': 'bundle_id', 'layer': ['clone']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': ['clone']}, ipc.get(['context', 'bundle_id'], reply='layer')) - self.assertEqual(['clone'], ipc.get(['context', 'bundle_id', 'layer'])) - self.assertEqual({ - 'layer': ['clone'], - 'type': ['activity'], - 'author': {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, - 'title': {'en-us': 'TestActivity'}, - }, - local['context'].get('bundle_id').properties(['layer', 'type', 'author', 'title'])) - self.assertEqual({ - 'context': 'bundle_id', - 'license': ['Public Domain'], - 'version': '1', - 'stability': 'stable', - }, - local['release'].get(impl).properties(['context', 'license', 'version', 'stability'])) - self.assertEqual({ - 'seqno': 5, - 'unpack_size': len(activity_info), - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'blob': blob_path, - 'mtime': int(os.stat(blob_path[:-5]).st_mtime), - 'mime_type': 'application/vnd.olpc-sugar', - 'spec': { - '*-*': { - 'requires': {}, - 'commands': {'activity': {'exec': 'true'}}, - }, - }, - }, - local['release'].get(impl).meta('data')) - self.assertEqual(activity_info, file(blob_path + '/activity/activity.info').read()) - assert exists(clone_path + '/data.blob/activity/activity.info') - self.assertEqual( - [client.api.value, ['stable'], downloaded_solution], - json.load(file('solutions/bu/bundle_id'))) - - self.assertEqual([ - ], - [i for i in ipc.put(['context', 'bundle_id'], False, cmd='clone')]) - - self.assertEqual({ - 'event': 'update', - 'guid': 'bundle_id', - 'resource': 'context', - }, - events[-1]) - self.assertEqual( - sorted([{'guid': 'bundle_id', 'layer': []}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - self.assertEqual({'layer': []}, ipc.get(['context', 'bundle_id'], reply='layer')) - self.assertEqual([], ipc.get(['context', 'bundle_id', 'layer'])) - self.assertEqual({ - 'layer': [], - 'type': ['activity'], - 'author': {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, - 'title': {'en-us': 'TestActivity'}, - }, - local['context'].get('bundle_id').properties(['layer', 'type', 'author', 'title'])) - self.assertEqual({ - 'seqno': 5, - 'unpack_size': len(activity_info), - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'blob': blob_path, - 'mtime': int(os.stat(blob_path[:-5]).st_mtime), - 'mime_type': 'application/vnd.olpc-sugar', - 'spec': { - '*-*': { - 'requires': {}, - 'commands': {'activity': {'exec': 'true'}}, - }, - }, - }, - local['release'].get(impl).meta('data')) - self.assertEqual(activity_info, file(blob_path + '/activity/activity.info').read()) - assert not exists(clone_path) - self.assertEqual( - [client.api.value, ['stable'], downloaded_solution], - json.load(file('solutions/bu/bundle_id'))) - - self.assertEqual([ - {'event': 'ready'}, - ], - [i for i in ipc.put(['context', 'bundle_id'], True, cmd='clone')]) - - self.assertEqual({ - 'event': 'update', - 'guid': 'bundle_id', - 'resource': 'context', - }, - events[-1]) - self.assertEqual( - sorted([{'guid': 'bundle_id', 'layer': ['clone']}]), - sorted(ipc.get(['context'], reply='layer')['result'])) - assert exists(clone_path + '/data.blob/activity/activity.info') - self.assertEqual( - [client.api.value, ['stable'], downloaded_solution], - json.load(file('solutions/bu/bundle_id'))) - - def test_clone_ActivityWithStabilityPreferences(self): - local = self.start_online_client([User, Context, Release]) - ipc = IPCConnection() - - activity_info1 = '\n'.join([ - '[Activity]', - 'name = TestActivity', - 'bundle_id = bundle_id', - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license = Public Domain', - ]) - blob1 = self.zips(['TestActivity/activity/activity.info', activity_info1]) - impl1 = ipc.upload(['release'], StringIO(blob1), cmd='submit', initial=True) - - activity_info2 = '\n'.join([ - '[Activity]', - 'name = TestActivity', - 'bundle_id = bundle_id', - 'exec = true', - 'icon = icon', - 'activity_version = 2', - 'license = Public Domain', - 'stability = buggy', - ]) - blob2 = self.zips(['TestActivity/activity/activity.info', activity_info2]) - impl2 = ipc.upload(['release'], StringIO(blob2), cmd='submit', initial=True) - - self.assertEqual( - 'ready', - [i for i in ipc.put(['context', 'bundle_id'], True, cmd='clone')][-1]['event']) - - coroutine.dispatch() - self.assertEqual({'layer': ['clone']}, ipc.get(['context', 'bundle_id'], reply='layer')) - self.assertEqual([impl1], [i.guid for i in local['release'].find()[0]]) - self.assertEqual(impl1, basename(os.readlink('client/context/bu/bundle_id/.clone'))) - - self.touch(('config', [ - '[stabilities]', - 'bundle_id = buggy stable', - ])) - Option.load(['config']) - - self.assertEqual( - [], - [i for i in ipc.put(['context', 'bundle_id'], False, cmd='clone')]) - self.assertEqual( - 'ready', - [i for i in ipc.put(['context', 'bundle_id'], True, cmd='clone')][-1]['event']) - - coroutine.dispatch() - self.assertEqual({'layer': ['clone']}, ipc.get(['context', 'bundle_id'], reply='layer')) - self.assertEqual([impl1, impl2], [i.guid for i in local['release'].find()[0]]) - self.assertEqual(impl2, basename(os.readlink('client/context/bu/bundle_id/.clone'))) - - def test_clone_Head(self): - local = self.start_online_client([User, Context, Release]) - ipc = IPCConnection() - - activity_info = '\n'.join([ - '[Activity]', - 'name = TestActivity', - 'bundle_id = bundle_id', - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license = Public Domain', - ]) - blob = self.zips(['TestActivity/activity/activity.info', activity_info]) - impl = ipc.upload(['release'], StringIO(blob), cmd='submit', initial=True) - blob_path = 'master/release/%s/%s/data.blob' % (impl[:2], impl) - - self.assertEqual({ - 'guid': impl, - 'license': ['Public Domain'], - 'stability': 'stable', - 'version': '1', - 'context': 'bundle_id', - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'application/vnd.olpc-sugar', - 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {}}}, - 'unpack_size': len(activity_info), - }, - }, - ipc.head(['context', 'bundle_id'], cmd='clone')) - - self.assertEqual( - 'ready', - [i for i in ipc.put(['context', 'bundle_id'], True, cmd='clone')][-1]['event']) - blob_path = tests.tmpdir + '/client/release/%s/%s/data.blob' % (impl[:2], impl) - - self.assertEqual({ - 'guid': impl, - 'license': ['Public Domain'], - 'stability': 'stable', - 'version': '1', - 'context': 'bundle_id', - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'blob': blob_path, - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'application/vnd.olpc-sugar', - 'mtime': int(os.stat(blob_path[:-5]).st_mtime), - 'seqno': 5, - 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {}}}, - 'unpack_size': len(activity_info), - }, - }, - ipc.head(['context', 'bundle_id'], cmd='clone')) - - def test_launch_Activity(self): - local = self.start_online_client([User, Context, Release]) - ipc = IPCConnection() - - activity_info = '\n'.join([ - '[Activity]', - 'name = TestActivity', - 'bundle_id = bundle_id', - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license=Public Domain', - ]) - blob = self.zips(['TestActivity/activity/activity.info', activity_info]) - impl = ipc.upload(['release'], StringIO(blob), cmd='submit', initial=True) - coroutine.sleep(.1) - - solution = [{ - 'guid': impl, - 'context': 'bundle_id', - 'license': ['Public Domain'], - 'stability': 'stable', - 'version': '1', - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'unpack_size': len(activity_info), - 'mime_type': 'application/vnd.olpc-sugar', - 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {}}}, - }, - }] - downloaded_solution = copy.deepcopy(solution) - downloaded_solution[0]['path'] = tests.tmpdir + '/client/release/%s/%s/data.blob' % (impl[:2], impl) - log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id.log' - self.assertEqual([ - {'event': 'launch', 'foo': 'bar', 'activity_id': 'activity_id'}, - {'event': 'exec', 'activity_id': 'activity_id'}, - {'event': 'exit', 'activity_id': 'activity_id'}, - ], - [i for i in ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar')]) - assert local['release'].exists(impl) - self.assertEqual( - [client.api.value, ['stable'], downloaded_solution], - json.load(file('solutions/bu/bundle_id'))) - - blob = self.zips(['TestActivity/activity/activity.info', [ - '[Activity]', - 'name = TestActivity', - 'bundle_id = bundle_id', - 'exec = true', - 'icon = icon', - 'activity_version = 2', - 'license=Public Domain', - ]]) - impl = ipc.upload(['release'], StringIO(blob), cmd='submit') - coroutine.sleep(.1) - - shutil.rmtree('solutions') - solution = [{ - 'guid': impl, - 'context': 'bundle_id', - 'license': ['Public Domain'], - 'stability': 'stable', - 'version': '2', - 'path': tests.tmpdir + '/client/release/%s/%s/data.blob' % (impl[:2], impl), - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'unpack_size': len(activity_info), - 'mime_type': 'application/vnd.olpc-sugar', - 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {}}}, - }, - }] - log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id_1.log' - self.assertEqual([ - {'event': 'launch', 'foo': 'bar', 'activity_id': 'activity_id'}, - {'event': 'exec', 'activity_id': 'activity_id'}, - {'event': 'exit', 'activity_id': 'activity_id'}, - ], - [i for i in ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar')]) - assert local['release'].exists(impl) - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/bu/bundle_id'))) - - self.node.stop() - coroutine.sleep(.1) - - log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id_2.log' - self.assertEqual([ - {'event': 'launch', 'foo': 'bar', 'activity_id': 'activity_id'}, - {'event': 'exec', 'activity_id': 'activity_id'}, - {'event': 'exit', 'activity_id': 'activity_id'}, - ], - [i for i in ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar')]) - assert local['release'].exists(impl) - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/bu/bundle_id'))) - - shutil.rmtree('solutions') - log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id_3.log' - self.assertEqual([ - {'event': 'launch', 'foo': 'bar', 'activity_id': 'activity_id'}, - {'event': 'exec', 'activity_id': 'activity_id'}, - {'event': 'exit', 'activity_id': 'activity_id'}, - ], - [i for i in ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar')]) - assert local['release'].exists(impl) - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/bu/bundle_id'))) - - def test_launch_Fails(self): - local = self.start_online_client([User, Context, Release]) - ipc = IPCConnection() - - self.assertEqual([ - {'event': 'failure', 'activity_id': 'activity_id', 'exception': 'NotFound', 'error': "Resource 'foo' does not exist in 'context'"}, - ], - [i for i in ipc.get(['context', 'foo'], cmd='launch')]) - - ipc.post(['context'], { - 'guid': 'bundle_id', - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - self.assertEqual([ - {'event': 'launch', 'activity_id': 'activity_id', 'foo': 'bar', 'activity_id': 'activity_id'}, - {'event': 'failure', 'activity_id': 'activity_id', 'exception': 'NotFound', - 'stability': ['stable'], - 'logs': [ - tests.tmpdir + '/.sugar/default/logs/shell.log', - tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', - ], - 'error': """\ -Can't find all required implementations: -- bundle_id -> (problem) - No known implementations at all"""}, - ], - [i for i in ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar')]) - - activity_info = '\n'.join([ - '[Activity]', - 'name = TestActivity', - 'bundle_id = bundle_id', - 'exec = false', - 'icon = icon', - 'activity_version = 1', - 'license=Public Domain', - ]) - blob = self.zips(['TestActivity/activity/activity.info', activity_info]) - impl = ipc.upload(['release'], StringIO(blob), cmd='submit', initial=True) - - solution = [{ - 'guid': impl, - 'context': 'bundle_id', - 'license': ['Public Domain'], - 'stability': 'stable', - 'version': '1', - 'path': tests.tmpdir + '/client/release/%s/%s/data.blob' % (impl[:2], impl), - 'layer': ['origin'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': { - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'unpack_size': len(activity_info), - 'mime_type': 'application/vnd.olpc-sugar', - 'spec': {'*-*': {'commands': {'activity': {'exec': 'false'}}, 'requires': {}}}, - }, - }] - self.assertEqual([ - {'event': 'launch', 'foo': 'bar', 'activity_id': 'activity_id'}, - {'event': 'exec', 'activity_id': 'activity_id'}, - {'event': 'failure', 'activity_id': 'activity_id', 'exception': 'RuntimeError', 'error': 'Process exited with 1 status', - 'stability': ['stable'], - 'args': ['false', '-b', 'bundle_id', '-a', 'activity_id'], - 'solution': solution, - 'logs': [ - tests.tmpdir + '/.sugar/default/logs/shell.log', - tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', - tests.tmpdir + '/.sugar/default/logs/bundle_id.log', - ]}, - ], - [i for i in ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar')]) - assert local['release'].exists(impl) - self.assertEqual( - [client.api.value, ['stable'], solution], - json.load(file('solutions/bu/bundle_id'))) - - def test_InvalidateSolutions(self): - self.start_online_client() - ipc = IPCConnection() - self.assertNotEqual(None, self.client_routes._node_mtime) - - mtime = self.client_routes._node_mtime - coroutine.sleep(1.1) - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - assert self.client_routes._node_mtime == mtime - - coroutine.sleep(1.1) - - impl1 = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['release'].update(impl1, {'data': { - 'spec': {'*-*': {}}, - }}) - assert self.client_routes._node_mtime > mtime - - mtime = self.client_routes._node_mtime - coroutine.sleep(1.1) - - impl2 = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '2', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['release'].update(impl2, {'data': { - 'spec': {'*-*': { - 'requires': { - 'dep1': {}, - 'dep2': {'restrictions': [['1', '2']]}, - 'dep3': {'restrictions': [[None, '2']]}, - 'dep4': {'restrictions': [['3', None]]}, - }, - }}, - }}) - assert self.client_routes._node_mtime > mtime - - def test_NoNeedlessRemoteRequests(self): - home_volume = self.start_online_client() - ipc = IPCConnection() - - guid = ipc.post(['context'], { - 'type': 'book', - 'title': 'remote', - 'summary': 'summary', - 'description': 'description', - }) - self.assertEqual( - {'title': 'remote'}, - ipc.get(['context', guid], reply=['title'])) - - home_volume['context'].create({ - 'guid': guid, - 'type': 'activity', - 'title': 'local', - 'summary': 'summary', - 'description': 'description', - }) - self.assertEqual( - {'title': 'local'}, - ipc.get(['context', guid], reply=['title'])) - - def test_RestrictLayers(self): - self.start_online_client([User, Context, Release]) - ipc = IPCConnection() - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - 'layer': 'public', - }) - impl = ipc.post(['release'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - 'layer': 'public', - }) - self.node_volume['release'].update(impl, {'data': { - 'spec': {'*-*': {}}, - }}) - - self.assertEqual( - [{'guid': context, 'layer': ['public']}], - ipc.get(['context'], reply=['guid', 'layer'])['result']) - self.assertEqual( - [], - ipc.get(['context'], reply=['guid', 'layer'], layer='foo')['result']) - self.assertEqual( - [{'guid': context, 'layer': ['public']}], - ipc.get(['context'], reply=['guid', 'layer'], layer='public')['result']) - - self.assertEqual( - [{'guid': impl, 'layer': ['origin', 'public']}], - ipc.get(['release'], reply=['guid', 'layer'])['result']) - self.assertEqual( - [], - ipc.get(['release'], reply=['guid', 'layer'], layer='foo')['result']) - self.assertEqual( - [{'guid': impl, 'layer': ['origin', 'public']}], - ipc.get(['release'], reply=['guid', 'layer'], layer='public')['result']) - - self.assertEqual({ - 'releases': [{ - 'stability': 'stable', - 'guid': impl, - 'version': '1', - 'license': ['GPLv3+'], - 'layer': ['origin', 'public'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': {'spec': {'*-*': {}}}, - }], - }, - ipc.get(['context', context], cmd='feed')) - self.assertEqual({ - 'releases': [], - }, - ipc.get(['context', context], cmd='feed', layer='foo')) - self.assertEqual({ - 'releases': [{ - 'stability': 'stable', - 'guid': impl, - 'version': '1', - 'license': ['GPLv3+'], - 'layer': ['origin', 'public'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': {'spec': {'*-*': {}}}, - }], - }, - ipc.get(['context', context], cmd='feed', layer='public')) - - client.layers.value = ['foo', 'bar'] - - self.assertEqual( - [], - ipc.get(['context'], reply=['guid', 'layer'])['result']) - self.assertEqual( - [], - ipc.get(['context'], reply=['guid', 'layer'], layer='foo')['result']) - self.assertEqual( - [{'guid': context, 'layer': ['public']}], - ipc.get(['context'], reply=['guid', 'layer'], layer='public')['result']) - - self.assertEqual( - [], - ipc.get(['release'], reply=['guid', 'layer'])['result']) - self.assertEqual( - [], - ipc.get(['release'], reply=['guid', 'layer'], layer='foo')['result']) - self.assertEqual( - [{'guid': impl, 'layer': ['origin', 'public']}], - ipc.get(['release'], reply=['guid', 'layer'], layer='public')['result']) - - self.assertEqual({ - 'releases': [], - }, - ipc.get(['context', context], cmd='feed')) - self.assertEqual({ - 'releases': [], - }, - ipc.get(['context', context], cmd='feed', layer='foo')) - self.assertEqual({ - 'releases': [{ - 'stability': 'stable', - 'guid': impl, - 'version': '1', - 'license': ['GPLv3+'], - 'layer': ['origin', 'public'], - 'author': {tests.UID: {'name': 'test', 'order': 0, 'role': 3}}, - 'ctime': self.node_volume['release'].get(impl).ctime, - 'notes': {'en-us': ''}, - 'tags': [], - 'data': {'spec': {'*-*': {}}}, - }], - }, - ipc.get(['context', context], cmd='feed', layer='public')) - - def test_Redirects(self): - - class Document(Resource): - - @db.blob_property() - def blob1(self, value): - raise http.Redirect(prefix + 'blob2') - - @db.blob_property() - def blob3(self, value): - raise http.Redirect(client.api.value + prefix + 'blob4') - - self.start_online_client([User, Document]) - ipc = IPCConnection() - guid = ipc.post(['document'], {}) - prefix = '/document/' + guid + '/' - - response = requests.request('GET', client.api.value + prefix + 'blob1', allow_redirects=False) - self.assertEqual(303, response.status_code) - self.assertEqual(prefix + 'blob2', response.headers['Location']) - - response = requests.request('GET', client.api.value + prefix + 'blob3', allow_redirects=False) - self.assertEqual(303, response.status_code) - self.assertEqual(client.api.value + prefix + 'blob4', response.headers['Location']) - - def test_DoNotSwitchToOfflineOnRedirectFails(self): - - class Document(Resource): - - @db.blob_property() - def blob1(self, value): - raise http.Redirect(prefix + '/blob2') - - @db.blob_property() - def blob2(self, value): - raise http._ConnectionError() - - local_volume = self.start_online_client([User, Document]) - ipc = IPCConnection() - guid = ipc.post(['document'], {}) - prefix = client.api.value + '/document/' + guid + '/' - local_volume['document'].create({'guid': guid}) - - trigger = self.wait_for_events(ipc, event='inline', state='connecting') - try: - ipc.get(['document', guid, 'blob1']) - except Exception: - pass - assert trigger.wait(.1) is None - - trigger = self.wait_for_events(ipc, event='inline', state='connecting') - try: - ipc.get(['document', guid, 'blob2']) - except Exception: - pass - assert trigger.wait(.1) is not None - - def test_ContentDisposition(self): - self.start_online_client([User, Context, Release, Post]) - ipc = IPCConnection() - - post = ipc.post(['post'], { - 'type': 'object', - 'context': 'context', - 'title': 'title', - 'message': '', - }) - ipc.request('PUT', ['post', post, 'data'], 'blob', headers={'Content-Type': 'image/png'}) - - response = ipc.request('GET', ['post', post, 'data']) - self.assertEqual( - 'attachment; filename="Title.png"', - response.headers.get('Content-Disposition')) - - def test_FallbackToLocalSNOnRemoteTransportFails(self): - - class LocalRoutes(routes._LocalRoutes): - - @route('GET', cmd='sleep') - def sleep(self): - return 'local' - - @route('GET', cmd='yield_raw_and_sleep', - mime_type='application/octet-stream') - def yield_raw_and_sleep(self): - yield 'local' - - @route('GET', cmd='yield_json_and_sleep', - mime_type='application/json') - def yield_json_and_sleep(self): - yield '"local"' - - self.override(routes, '_LocalRoutes', LocalRoutes) - home_volume = self.start_client() - ipc = IPCConnection() - - self.assertEqual('local', ipc.get(cmd='sleep')) - self.assertEqual('local', ipc.get(cmd='yield_raw_and_sleep')) - self.assertEqual('local', ipc.get(cmd='yield_json_and_sleep')) - - class NodeRoutes(MasterRoutes): - - @route('GET', cmd='sleep') - def sleep(self): - coroutine.sleep(.5) - return 'remote' - - @route('GET', cmd='yield_raw_and_sleep', - mime_type='application/octet-stream') - def yield_raw_and_sleep(self): - for __ in range(33): - yield "remote\n" - coroutine.sleep(.5) - for __ in range(33): - yield "remote\n" - - @route('GET', cmd='yield_json_and_sleep', - mime_type='application/json') - def yield_json_and_sleep(self): - yield '"' - yield 'r' - coroutine.sleep(1) - yield 'emote"' - - node_pid = self.fork_master([User], NodeRoutes) - self.client_routes._remote_connect() - self.wait_for_events(ipc, event='inline', state='online').wait() - - ts = time.time() - self.assertEqual('remote', ipc.get(cmd='sleep')) - self.assertEqual('remote\n' * 66, ipc.get(cmd='yield_raw_and_sleep')) - self.assertEqual('remote', ipc.get(cmd='yield_json_and_sleep')) - assert time.time() - ts >= 2 - - def kill(): - coroutine.sleep(.5) - self.waitpid(node_pid) - - coroutine.spawn(kill) - self.assertEqual('local', ipc.get(cmd='sleep')) - assert not ipc.get(cmd='inline') - - node_pid = self.fork_master([User], NodeRoutes) - self.client_routes._remote_connect() - self.wait_for_events(ipc, event='inline', state='online').wait() - - coroutine.spawn(kill) - self.assertEqual('local', ipc.get(cmd='yield_raw_and_sleep')) - assert not ipc.get(cmd='inline') - - node_pid = self.fork_master([User], NodeRoutes) - self.client_routes._remote_connect() - self.wait_for_events(ipc, event='inline', state='online').wait() - - coroutine.spawn(kill) - self.assertEqual('local', ipc.get(cmd='yield_json_and_sleep')) - assert not ipc.get(cmd='inline') - - def test_ReconnectOnServerFall(self): - routes._RECONNECT_TIMEOUT = 1 - - node_pid = self.fork_master() - self.start_client() - ipc = IPCConnection() - self.wait_for_events(ipc, event='inline', state='online').wait() - - def shutdown(): - coroutine.sleep(.1) - self.waitpid(node_pid) - coroutine.spawn(shutdown) - self.wait_for_events(ipc, event='inline', state='offline').wait() - - self.fork_master() - self.wait_for_events(ipc, event='inline', state='online').wait() - - def test_SilentReconnectOnGatewayErrors(self): - - class Routes(object): - - subscribe_tries = 0 - - def __init__(self, *args): - pass - - @route('GET', cmd='status', mime_type='application/json') - def info(self): - return {'resources': {}} - - @route('GET', cmd='subscribe', mime_type='text/event-stream') - def subscribe(self, request=None, response=None, ping=False, **condition): - Routes.subscribe_tries += 1 - coroutine.sleep(.1) - if Routes.subscribe_tries % 2: - raise http.BadGateway() - else: - raise http.GatewayTimeout() - - node_pid = self.start_master(None, Routes) - self.start_client() - ipc = IPCConnection() - self.wait_for_events(ipc, event='inline', state='online').wait() - - def read_events(): - for event in ipc.subscribe(): - events.append(event) - events = [] - coroutine.spawn(read_events) - - coroutine.sleep(1) - self.assertEqual([], events) - assert Routes.subscribe_tries > 2 - - def test_inline(self): - routes._RECONNECT_TIMEOUT = 2 - - cp = ClientRoutes(Volume('client', model.RESOURCES), client.api.value) - assert not cp.inline() - - trigger = self.wait_for_events(cp, event='inline', state='online') - coroutine.sleep(.5) - self.start_master() - trigger.wait(.5) - assert trigger.value is None - assert not cp.inline() - - trigger.wait() - assert cp.inline() - - trigger = self.wait_for_events(cp, event='inline', state='offline') - self.node.stop() - trigger.wait() - assert not cp.inline() - - def test_SubmitReport(self): - self.home_volume = self.start_online_client([User, Context, Release, Report]) - ipc = IPCConnection() - - self.touch( - ['file1', 'content1'], - ['file2', 'content2'], - ['file3', 'content3'], - ) - events = [i for i in ipc.post(['report'], {'context': 'context', 'error': 'error', 'logs': [ - tests.tmpdir + '/file1', - tests.tmpdir + '/file2', - tests.tmpdir + '/file3', - ]}, cmd='submit')] - self.assertEqual('done', events[-1]['event']) - guid = events[-1]['guid'] - - self.assertEqual({ - 'context': 'context', - 'error': 'error', - }, - ipc.get(['report', guid], reply=['context', 'error'])) - zipfile = ZipFile('master/report/%s/%s/data.blob' % (guid[:2], guid)) - self.assertEqual('content1', zipfile.read('file1')) - self.assertEqual('content2', zipfile.read('file2')) - self.assertEqual('content3', zipfile.read('file3')) - - -if __name__ == '__main__': - tests.main() diff --git a/tests/units/client/routes.py b/tests/units/client/routes.py index 7cd03e6..325ac99 100755 --- a/tests/units/client/routes.py +++ b/tests/units/client/routes.py @@ -5,18 +5,22 @@ import os import json import time +import hashlib from cStringIO import StringIO from os.path import exists from __init__ import tests -from sugar_network import db, client, model, toolkit -from sugar_network.client import journal, IPCConnection, cache_limit, cache_lifetime +from sugar_network import db, client, toolkit +from sugar_network.client import journal, IPCConnection, cache_limit, cache_lifetime, api, injector, routes +from sugar_network.client.model import RESOURCES +from sugar_network.client.injector import Injector from sugar_network.client.routes import ClientRoutes, CachedClientRoutes -from sugar_network.model.user import User -from sugar_network.model.report import Report -from sugar_network.toolkit.router import Router, Request, Response -from sugar_network.toolkit import coroutine, i18n +from sugar_network.node.model import User +from sugar_network.node.master import MasterRoutes +from sugar_network.toolkit.router import Router, Request, Response, route +from sugar_network.toolkit.coroutine import this +from sugar_network.toolkit import coroutine, i18n, parcel, http import requests @@ -24,7 +28,7 @@ import requests class RoutesTest(tests.Test): def test_Hub(self): - volume = db.Volume('db', model.RESOURCES) + volume = db.Volume('db', RESOURCES) cp = ClientRoutes(volume) server = coroutine.WSGIServer( ('127.0.0.1', client.ipc_port.value), Router(cp)) @@ -47,8 +51,305 @@ class RoutesTest(tests.Test): response = requests.request('GET', url + '/hub/', allow_redirects=False) self.assertEqual(index_html, response.content) - def test_LocalLayers(self): - self.home_volume = self.start_online_client() + def test_I18nQuery(self): + os.environ['LANGUAGE'] = 'foo' + self.start_online_client() + ipc = IPCConnection() + + ipc.request('POST', [], ''.join(parcel.encode([ + ('push', None, [ + {'resource': 'context'}, + {'guid': '1', 'patch': { + 'guid': {'value': '1', 'mtime': 1}, + 'ctime': {'value': 1, 'mtime': 1}, + 'mtime': {'value': 1, 'mtime': 1}, + 'type': {'value': ['activity'], 'mtime': 1}, + 'summary': {'value': {}, 'mtime': 1}, + 'description': {'value': {}, 'mtime': 1}, + 'title': {'value': {'en-US': 'qwe', 'ru-RU': 'йцу'}, 'mtime': 1}, + }}, + {'guid': '2', 'patch': { + 'guid': {'value': '2', 'mtime': 1}, + 'ctime': {'value': 1, 'mtime': 1}, + 'mtime': {'value': 1, 'mtime': 1}, + 'type': {'value': ['activity'], 'mtime': 1}, + 'summary': {'value': {}, 'mtime': 1}, + 'description': {'value': {}, 'mtime': 1}, + 'title': {'value': {'en-US': 'qwerty', 'ru-RU': 'йцукен'}, 'mtime': 1}, + }}, + ]), + ], header={'to': '127.0.0.1:7777', 'from': 'slave'})), params={'cmd': 'push'}) + + self.assertEqual([ + {'guid': '1'}, + {'guid': '2'}, + ], + ipc.get(['context'], query='йцу')['result']) + self.assertEqual([ + {'guid': '1'}, + {'guid': '2'}, + ], + ipc.get(['context'], query='qwe')['result']) + + self.assertEqual([ + {'guid': '2'}, + ], + ipc.get(['context'], query='йцукен')['result']) + self.assertEqual([ + {'guid': '2'}, + ], + ipc.get(['context'], query='qwerty')['result']) + + def test_LanguagesFallbackInRequests(self): + self.start_online_client() + ipc = IPCConnection() + + ipc.request('POST', [], ''.join(parcel.encode([ + ('push', None, [ + {'resource': 'context'}, + {'guid': '1', 'patch': { + 'guid': {'value': '1', 'mtime': 1}, + 'ctime': {'value': 1, 'mtime': 1}, + 'mtime': {'value': 1, 'mtime': 1}, + 'type': {'value': ['activity'], 'mtime': 1}, + 'summary': {'value': {}, 'mtime': 1}, + 'description': {'value': {}, 'mtime': 1}, + 'title': {'value': {'en': '1', 'ru': '2', 'es': '3'}, 'mtime': 1}, + }}, + {'guid': '2', 'patch': { + 'guid': {'value': '2', 'mtime': 1}, + 'ctime': {'value': 1, 'mtime': 1}, + 'mtime': {'value': 1, 'mtime': 1}, + 'type': {'value': ['activity'], 'mtime': 1}, + 'summary': {'value': {}, 'mtime': 1}, + 'description': {'value': {}, 'mtime': 1}, + 'title': {'value': {'en': '1', 'ru': '2'}, 'mtime': 1}, + }}, + {'guid': '3', 'patch': { + 'guid': {'value': '3', 'mtime': 1}, + 'ctime': {'value': 1, 'mtime': 1}, + 'mtime': {'value': 1, 'mtime': 1}, + 'type': {'value': ['activity'], 'mtime': 1}, + 'summary': {'value': {}, 'mtime': 1}, + 'description': {'value': {}, 'mtime': 1}, + 'title': {'value': {'en': '1'}, 'mtime': 1}, + }}, + ]), + ], header={'to': '127.0.0.1:7777', 'from': 'slave'})), params={'cmd': 'push'}) + + i18n._default_langs = None + os.environ['LANGUAGE'] = 'es:ru:en' + ipc = IPCConnection() + self.assertEqual('3', ipc.get(['context', '1', 'title'])) + self.assertEqual('2', ipc.get(['context', '2', 'title'])) + self.assertEqual('1', ipc.get(['context', '3', 'title'])) + + i18n._default_langs = None + os.environ['LANGUAGE'] = 'ru:en' + ipc = IPCConnection() + self.assertEqual('2', ipc.get(['context', '1', 'title'])) + self.assertEqual('2', ipc.get(['context', '2', 'title'])) + self.assertEqual('1', ipc.get(['context', '3', 'title'])) + + i18n._default_langs = None + os.environ['LANGUAGE'] = 'en' + ipc = IPCConnection() + self.assertEqual('1', ipc.get(['context', '1', 'title'])) + self.assertEqual('1', ipc.get(['context', '2', 'title'])) + self.assertEqual('1', ipc.get(['context', '3', 'title'])) + + i18n._default_langs = None + os.environ['LANGUAGE'] = 'foo' + ipc = IPCConnection() + self.assertEqual('1', ipc.get(['context', '1', 'title'])) + self.assertEqual('1', ipc.get(['context', '2', 'title'])) + self.assertEqual('1', ipc.get(['context', '3', 'title'])) + + def test_whoami(self): + self.start_offline_client() + ipc = IPCConnection() + + self.assertEqual( + {'guid': tests.UID, 'roles': [], 'route': 'offline'}, + ipc.get(cmd='whoami')) + + self.fork_master() + self.wait_for_events(event='inline', state='online').wait() + + self.assertEqual( + {'guid': tests.UID, 'roles': [], 'route': 'proxy'}, + ipc.get(cmd='whoami')) + + def test_Events(self): + self.override(time, 'time', lambda: 0) + self.start_offline_client() + ipc = IPCConnection() + events = [] + + def read_events(): + for event in ipc.subscribe(): + if event['event'] not in ('commit', 'pong'): + events.append(event) + coroutine.spawn(read_events) + coroutine.dispatch() + + guid = ipc.post(['context'], { + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + ipc.put(['context', guid], { + 'title': 'title_2', + }) + ipc.delete(['context', guid]) + coroutine.sleep(.1) + + self.assertEqual([ + {'event': 'create', 'guid': guid, 'resource': 'context'}, + {'event': 'update', 'guid': guid, 'resource': 'context', 'props': {'mtime': 0, 'title': {'en-us': 'title_2'}}}, + {'event': 'delete', 'guid': guid, 'resource': 'context'}, + ], + events) + del events[:] + + self.fork_master() + self.wait_for_events(event='inline', state='online').wait() + coroutine.sleep(.1) + + self.assertEqual([ + {'event': 'inline', 'state': 'connecting'}, + {'event': 'inline', 'state': 'online'}, + ], + events) + del events[:] + + guid = ipc.post(['context'], { + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + ipc.put(['context', guid], { + 'title': 'title_2', + }) + coroutine.sleep(.1) + ipc.delete(['context', guid]) + coroutine.sleep(.1) + + self.assertEqual([ + {'event': 'create', 'guid': tests.UID, 'resource': 'user'}, + {'event': 'create', 'guid': guid, 'resource': 'context'}, + {'event': 'update', 'guid': guid, 'resource': 'context', 'props': {'mtime': 0, 'title': {'en-us': 'title_2'}}}, + {'event': 'delete', 'guid': guid, 'resource': 'context'}, + ], + events) + del events[:] + + def test_HomeVolumeEventsOnlyInOffline(self): + home_volume = self.start_offline_client() + ipc = IPCConnection() + events = [] + + def read_events(): + for event in ipc.subscribe(): + if event['event'] not in ('commit', 'pong'): + events.append(event) + coroutine.spawn(read_events) + coroutine.sleep(.1) + + guid = home_volume['context'].create({ + 'type': ['activity'], + 'title': {}, + 'summary': {}, + 'description': {}, + }) + home_volume['context'].update(guid, { + 'title': {'en': 'title_2'}, + }) + home_volume['context'].delete(guid) + coroutine.sleep(.1) + + self.assertEqual([ + {'guid': guid, 'resource': 'context', 'event': 'create'}, + {'guid': guid, 'resource': 'context', 'event': 'update', 'props': {'title': {'en': 'title_2'}}}, + {'guid': guid, 'event': 'delete', 'resource': 'context'}, + ], + events) + del events[:] + + self.fork_master() + self.wait_for_events(event='inline', state='online').wait() + coroutine.sleep(.1) + del events[:] + + guid = home_volume['context'].create({ + 'type': ['activity'], + 'title': {}, + 'summary': {}, + 'description': {}, + }) + home_volume['context'].update(guid, { + 'title': {'en': 'title_2'}, + }) + coroutine.sleep(.1) + home_volume['context'].delete(guid) + coroutine.sleep(.1) + + self.assertEqual([], events) + + def test_BLOBs(self): + self.start_offline_client() + ipc = IPCConnection() + + blob = 'blob_value' + digest = hashlib.sha1(blob).hexdigest() + + guid = ipc.post(['context'], { + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + ipc.request('PUT', ['context', guid, 'logo'], blob, headers={'content-type': 'image/png'}) + + self.assertEqual( + blob, + ipc.request('GET', ['context', guid, 'logo']).content) + self.assertEqual({ + 'logo': 'http://127.0.0.1:5555/blobs/%s' % digest, + }, + ipc.get(['context', guid], reply=['logo'])) + self.assertEqual([{ + 'logo': 'http://127.0.0.1:5555/blobs/%s' % digest, + }], + ipc.get(['context'], reply=['logo'])['result']) + + self.fork_master() + self.wait_for_events(event='inline', state='online').wait() + + guid = ipc.post(['context'], { + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + ipc.request('PUT', ['context', guid, 'logo'], blob, headers={'content-type': 'image/png'}) + + self.assertEqual( + blob, + ipc.request('GET', ['context', guid, 'logo']).content) + self.assertEqual({ + 'logo': 'http://127.0.0.1:7777/blobs/%s' % digest, + }, + ipc.get(['context', guid], reply=['logo'])) + self.assertEqual([{ + 'logo': 'http://127.0.0.1:7777/blobs/%s' % digest, + }], + ipc.get(['context'], reply=['logo'])['result']) + + def test_OnlinePins(self): + home_volume = self.start_online_client() ipc = IPCConnection() guid1 = ipc.post(['context'], { @@ -58,7 +359,7 @@ class RoutesTest(tests.Test): 'summary': 'summary', 'description': 'description', }) - ipc.upload(['release'], StringIO(self.zips(['TestActivity/activity/activity.info', [ + ipc.upload(['context'], self.zips(['TestActivity/activity/activity.info', [ '[Activity]', 'name = 2', 'bundle_id = context2', @@ -67,9 +368,9 @@ class RoutesTest(tests.Test): 'activity_version = 1', 'license = Public Domain', 'stability = stable', - ]])), cmd='submit', initial=True) + ]]), cmd='submit', initial=True) guid2 = 'context2' - ipc.upload(['release'], StringIO(self.zips(['TestActivity/activity/activity.info', [ + ipc.upload(['context'], self.zips(['TestActivity/activity/activity.info', [ '[Activity]', 'name = 3', 'bundle_id = context3', @@ -78,7 +379,7 @@ class RoutesTest(tests.Test): 'activity_version = 1', 'license = Public Domain', 'stability = stable', - ]])), cmd='submit', initial=True) + ]]), cmd='submit', initial=True) guid3 = 'context3' guid4 = ipc.post(['context'], { 'guid': 'context4', @@ -89,70 +390,722 @@ class RoutesTest(tests.Test): }) self.assertEqual([ - {'guid': guid1, 'title': '1', 'layer': []}, - {'guid': guid2, 'title': '2', 'layer': []}, - {'guid': guid3, 'title': '3', 'layer': []}, - {'guid': guid4, 'title': '4', 'layer': []}, + {'guid': guid1, 'title': '1', 'pins': []}, + {'guid': guid2, 'title': '2', 'pins': []}, + {'guid': guid3, 'title': '3', 'pins': []}, + {'guid': guid4, 'title': '4', 'pins': []}, ], - ipc.get(['context'], reply=['guid', 'title', 'layer'])['result']) + ipc.get(['context'], reply=['guid', 'title', 'pins'])['result']) self.assertEqual([ ], - ipc.get(['context'], reply=['guid', 'title'], layer='favorite')['result']) + ipc.get(['context'], reply=['guid', 'title'], pins='favorite')['result']) self.assertEqual([ ], - ipc.get(['context'], reply=['guid', 'title'], layer='clone')['result']) + ipc.get(['context'], reply=['guid', 'title'], pins='checkin')['result']) ipc.put(['context', guid1], True, cmd='favorite') ipc.put(['context', guid2], True, cmd='favorite') - ipc.put(['context', guid2], True, cmd='clone') - ipc.put(['context', guid3], True, cmd='clone') - self.home_volume['context'].update(guid1, {'title': '1_'}) - self.home_volume['context'].update(guid2, {'title': '2_'}) - self.home_volume['context'].update(guid3, {'title': '3_'}) + self.assertEqual([ + {'event': 'checkin', 'state': 'solve'}, + {'event': 'checkin', 'state': 'download'}, + {'event': 'checkin', 'state': 'ready'}, + ], + [i for i in ipc.put(['context', guid2], True, cmd='checkin')]) + self.assertEqual([ + {'event': 'checkin', 'state': 'solve'}, + {'event': 'checkin', 'state': 'download'}, + {'event': 'checkin', 'state': 'ready'}, + ], + [i for i in ipc.put(['context', guid3], True, cmd='checkin')]) + home_volume['context'].update(guid1, {'title': {i18n.default_lang(): '1_'}}) + home_volume['context'].update(guid2, {'title': {i18n.default_lang(): '2_'}}) + home_volume['context'].update(guid3, {'title': {i18n.default_lang(): '3_'}}) self.assertEqual([ - {'guid': guid1, 'title': '1', 'layer': ['favorite']}, - {'guid': guid2, 'title': '2', 'layer': ['clone', 'favorite']}, - {'guid': guid3, 'title': '3', 'layer': ['clone']}, - {'guid': guid4, 'title': '4', 'layer': []}, + {'guid': guid1, 'title': '1', 'pins': ['favorite']}, + {'guid': guid2, 'title': '2', 'pins': ['checkin', 'favorite']}, + {'guid': guid3, 'title': '3', 'pins': ['checkin']}, + {'guid': guid4, 'title': '4', 'pins': []}, ], - ipc.get(['context'], reply=['guid', 'title', 'layer'])['result']) + ipc.get(['context'], reply=['guid', 'title', 'pins'])['result']) self.assertEqual([ {'guid': guid1, 'title': '1_'}, {'guid': guid2, 'title': '2_'}, ], - ipc.get(['context'], reply=['guid', 'title'], layer='favorite')['result']) + ipc.get(['context'], reply=['guid', 'title'], pins='favorite')['result']) self.assertEqual([ {'guid': guid2, 'title': '2_'}, {'guid': guid3, 'title': '3_'}, ], - ipc.get(['context'], reply=['guid', 'title'], layer='clone')['result']) + ipc.get(['context'], reply=['guid', 'title'], pins='checkin')['result']) - def test_SetLocalLayerInOffline(self): - volume = db.Volume('client', model.RESOURCES) - cp = ClientRoutes(volume, client.api.value) - post = Request(method='POST', path=['context']) - post.content_type = 'application/json' - post.content = { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - } + ipc.delete(['context', guid1], cmd='favorite') + ipc.delete(['context', guid2], cmd='checkin') - guid = call(cp, post) - self.assertEqual(['local'], call(cp, Request(method='GET', path=['context', guid, 'layer']))) + self.assertEqual([ + {'guid': guid1, 'pins': []}, + {'guid': guid2, 'pins': ['favorite']}, + {'guid': guid3, 'pins': ['checkin']}, + {'guid': guid4, 'pins': []}, + ], + ipc.get(['context'], reply=['guid', 'pins'])['result']) + self.assertEqual([ + {'guid': guid2, 'pins': ['favorite']}, + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='favorite')['result']) + self.assertEqual([ + {'guid': guid3, 'pins': ['checkin']}, + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='checkin')['result']) + + def test_OfflinePins(self): + self.start_online_client() + ipc = IPCConnection() + + ipc.upload(['context'], self.zips(['TestActivity/activity/activity.info', [ + '[Activity]', + 'name = 1', + 'bundle_id = 1', + 'exec = true', + 'icon = icon', + 'activity_version = 1', + 'license = Public Domain', + ]]), cmd='submit', initial=True) + ipc.upload(['context'], self.zips(['TestActivity/activity/activity.info', [ + '[Activity]', + 'name = 2', + 'bundle_id = 2', + 'exec = true', + 'icon = icon', + 'activity_version = 2', + 'license = Public Domain', + ]]), cmd='submit', initial=True) + ipc.upload(['context'], self.zips(['TestActivity/activity/activity.info', [ + '[Activity]', + 'name = 3', + 'bundle_id = 3', + 'exec = true', + 'icon = icon', + 'activity_version = 3', + 'license = Public Domain', + ]]), cmd='submit', initial=True) + + ipc.put(['context', '1'], None, cmd='favorite') + self.assertEqual([ + {'event': 'checkin', 'state': 'solve'}, + {'event': 'checkin', 'state': 'download'}, + {'event': 'checkin', 'state': 'ready'}, + ], + [i for i in ipc.put(['context', '2'], None, cmd='checkin')]) + self.assertEqual([ + {'guid': '1', 'pins': ['favorite']}, + {'guid': '2', 'pins': ['checkin']}, + {'guid': '3', 'pins': []}, + ], + ipc.get(['context'], reply=['guid', 'pins'])['result']) + + self.stop_master() + self.wait_for_events(event='inline', state='offline').wait() + + self.assertEqual([ + {'guid': '1', 'pins': ['favorite']}, + {'guid': '2', 'pins': ['checkin']}, + ], + ipc.get(['context'], reply=['guid', 'pins'])['result']) + self.assertEqual([ + {'guid': '1', 'pins': ['favorite']}, + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='favorite')['result']) + self.assertEqual([ + {'guid': '2', 'pins': ['checkin']}, + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='checkin')['result']) + + ipc.delete(['context', '1'], cmd='favorite') + ipc.put(['context', '2'], None, cmd='favorite') + self.assertRaises(http.ServiceUnavailable, ipc.put, ['context', '3'], None, cmd='favorite') + + self.assertEqual([ + {'guid': '1', 'pins': []}, + {'guid': '2', 'pins': ['checkin', 'favorite']}, + ], + ipc.get(['context'], reply=['guid', 'pins'])['result']) + self.assertEqual([ + {'guid': '2', 'pins': ['checkin', 'favorite']}, + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='favorite')['result']) + self.assertEqual([ + {'guid': '2', 'pins': ['checkin', 'favorite']}, + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='checkin')['result']) + + ipc.delete(['context', '2'], cmd='checkin') + ipc.delete(['context', '2'], cmd='favorite') + + self.assertEqual([ + {'guid': '1', 'pins': []}, + {'guid': '2', 'pins': []}, + ], + ipc.get(['context'], reply=['guid', 'pins'])['result']) + self.assertEqual([ + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='favorite')['result']) + self.assertEqual([ + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='checkin')['result']) + + self.assertEqual([ + {'event': 'checkin', 'state': 'solve'}, + {'event': 'failure', 'error': 'Not available in offline', 'exception': 'ServiceUnavailable'}, + ], + [i for i in ipc.put(['context', '1'], None, cmd='checkin')]) + ipc.put(['context', '1'], None, cmd='favorite') + self.assertEqual([ + {'event': 'checkin', 'state': 'solve'}, + {'event': 'checkin', 'state': 'ready'}, + ], + [i for i in ipc.put(['context', '2'], None, cmd='checkin')]) + ipc.put(['context', '2'], None, cmd='favorite') + self.assertEqual([ + {'event': 'failure', 'error': 'Not available in offline', 'exception': 'ServiceUnavailable'}, + ], + [i for i in ipc.put(['context', '3'], None, cmd='checkin')]) + self.assertRaises(http.ServiceUnavailable, ipc.put, ['context', '3'], None, cmd='favorite') + + self.assertEqual([ + {'guid': '1', 'pins': ['favorite']}, + {'guid': '2', 'pins': ['checkin', 'favorite']}, + ], + ipc.get(['context'], reply=['guid', 'pins'])['result']) + self.assertEqual([ + {'guid': '1', 'pins': ['favorite']}, + {'guid': '2', 'pins': ['checkin', 'favorite']}, + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='favorite')['result']) + self.assertEqual([ + {'guid': '2', 'pins': ['checkin', 'favorite']}, + ], + ipc.get(['context'], reply=['guid', 'pins'], pins='checkin')['result']) + + def test_checkin_Notificaitons(self): + self.start_online_client() + ipc = IPCConnection() + + activity_info = '\n'.join([ + '[Activity]', + 'name = Activity', + 'bundle_id = context', + 'exec = true', + 'icon = icon', + 'activity_version = 1', + 'license = Public Domain', + ]) + activity_bundle = self.zips(('topdir/activity/activity.info', activity_info)) + release = ipc.upload(['context'], activity_bundle, cmd='submit', initial=True) + + def subscribe(): + for i in ipc.subscribe(): + if i.get('event') != 'commit': + events.append(i) + events = [] + coroutine.spawn(subscribe) + coroutine.sleep(.1) + del events[:] + + assert {'event': 'checkin', 'state': 'ready'} in [i for i in ipc.put(['context', 'context'], None, cmd='checkin')] + self.assertEqual([ + {'event': 'update', 'guid': 'context', 'props': {'pins': ['inprogress']}, 'resource': 'context'}, + {'event': 'update', 'guid': 'context', 'props': {'pins': ['checkin']}, 'resource': 'context'}, + ], events) + del events[:] + + ipc.put(['context', 'context'], None, cmd='favorite') + ipc.delete(['context', 'context'], cmd='checkin') + coroutine.sleep(.1) + self.assertEqual([ + {'event': 'update', 'guid': 'context', 'props': {'pins': ['favorite']}, 'resource': 'context'}, + ], events) + + self.stop_master() + self.wait_for_events(event='inline', state='offline').wait() + coroutine.sleep(.1) + del events[:] + + assert {'event': 'checkin', 'state': 'ready'} in [i for i in ipc.put(['context', 'context'], None, cmd='checkin')] + self.assertEqual([ + {'event': 'update', 'guid': 'context', 'props': {'pins': ['favorite', 'inprogress']}, 'resource': 'context'}, + {'event': 'update', 'guid': 'context', 'props': {'pins': ['checkin', 'favorite']}, 'resource': 'context'}, + ], events) + del events[:] + + ipc.delete(['context', 'context'], cmd='checkin') + coroutine.sleep(.1) + self.assertEqual([ + {'event': 'update', 'guid': 'context', 'props': {'pins': ['favorite']}, 'resource': 'context'}, + ], events) + + def test_launch_Notificaitons(self): + self.start_online_client() + ipc = IPCConnection() + + activity_info = '\n'.join([ + '[Activity]', + 'name = Activity', + 'bundle_id = context', + 'exec = true', + 'icon = icon', + 'activity_version = 1', + 'license = Public Domain', + ]) + activity_bundle = self.zips(('topdir/activity/activity.info', activity_info)) + release = ipc.upload(['context'], activity_bundle, cmd='submit', initial=True) + + def subscribe(): + for i in ipc.subscribe(): + if i.get('event') != 'commit': + events.append(i) + events = [] + coroutine.spawn(subscribe) + coroutine.sleep(.1) + del events[:] + + assert {'event': 'launch', 'state': 'exit'} in [i for i in ipc.get(['context', 'context'], cmd='launch')] + self.assertEqual([ + {'event': 'update', 'guid': 'context', 'props': {'pins': ['inprogress']}, 'resource': 'context'}, + {'event': 'update', 'guid': 'context', 'props': {'pins': []}, 'resource': 'context'}, + ], events) + + ipc.put(['context', 'context'], None, cmd='favorite') + self.stop_master() + self.wait_for_events(event='inline', state='offline').wait() + coroutine.sleep(.1) + del events[:] + + assert {'event': 'launch', 'state': 'exit'} in [i for i in ipc.get(['context', 'context'], None, cmd='launch')] + self.assertEqual([ + {'event': 'update', 'guid': 'context', 'props': {'pins': ['favorite', 'inprogress']}, 'resource': 'context'}, + {'event': 'update', 'guid': 'context', 'props': {'pins': ['favorite']}, 'resource': 'context'}, + ], events) + del events[:] + + def test_checkin_Fails(self): + self.start_online_client() + ipc = IPCConnection() + + self.assertEqual([ + {'event': 'checkin', 'state': 'solve'}, + {'error': 'Context not found', 'event': 'failure', 'exception': 'NotFound'}, + ], + [i for i in ipc.put(['context', 'context'], None, cmd='checkin')]) + + guid = ipc.post(['context'], { + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + + self.assertEqual([ + {'event': 'checkin', 'state': 'solve'}, + {'error': 'Failed to solve', 'event': 'failure', 'exception': 'RuntimeError'}, + ], + [i for i in ipc.put(['context', guid], None, cmd='checkin')]) + + ipc.put(['context', guid], None, cmd='favorite') + self.stop_master() + self.wait_for_events(event='inline', state='offline').wait() + coroutine.sleep(.1) + + self.assertEqual([ + {'error': 'Not available in offline', 'event': 'failure', 'exception': 'ServiceUnavailable'}, + ], + [i for i in ipc.put(['context', 'context'], None, cmd='checkin')]) + + self.assertEqual([ + {'event': 'checkin', 'state': 'solve'}, + {'error': 'Not available in offline', 'event': 'failure', 'exception': 'ServiceUnavailable'}, + ], + [i for i in ipc.put(['context', guid], None, cmd='checkin')]) + + def test_launch_Fails(self): + self.override(injector, '_activity_id_new', lambda: 'activity_id') + self.start_online_client() + ipc = IPCConnection() + + self.assertEqual([ + {'activity_id': 'activity_id'}, + {'event': 'launch', 'state': 'init'}, + {'event': 'launch', 'state': 'solve'}, + {'error': 'Context not found', 'event': 'failure', 'exception': 'NotFound'}, + ], + [i for i in ipc.get(['context', 'context'], cmd='launch')]) + + guid1 = ipc.post(['context'], { + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + + self.assertEqual([ + {'activity_id': 'activity_id'}, + {'event': 'launch', 'state': 'init'}, + {'event': 'launch', 'state': 'solve'}, + {'error': 'Failed to solve', 'event': 'failure', 'exception': 'RuntimeError'}, + ], + [i for i in ipc.get(['context', guid1], cmd='launch')]) + + activity_info = '\n'.join([ + '[Activity]', + 'name = Activity', + 'bundle_id = context2', + 'exec = false', + 'icon = icon', + 'activity_version = 1', + 'license = Public Domain', + ]) + activity_bundle = self.zips(('topdir/activity/activity.info', activity_info)) + release = ipc.upload(['context'], activity_bundle, cmd='submit', initial=True) + + self.assertEqual([ + {'activity_id': 'activity_id'}, + {'event': 'launch', 'state': 'init'}, + {'event': 'launch', 'state': 'solve'}, + {'event': 'launch', 'state': 'download'}, + {'event': 'launch', 'state': 'exec'}, + {'context': 'context2', + 'args': ['false', '-b', 'context2', '-a', 'activity_id'], + 'logs': [ + tests.tmpdir + '/.sugar/default/logs/shell.log', + tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', + tests.tmpdir + '/.sugar/default/logs/context2.log', + ], + 'solution': { + 'context2': { + 'blob': release, + 'command': ['activity', 'false'], + 'content-type': 'application/vnd.olpc-sugar', + 'size': len(activity_bundle), + 'title': 'Activity', + 'unpack_size': len(activity_info), + 'version': [[1], 0], + }, + }, + }, + {'error': 'Process exited with 1 status', 'event': 'failure', 'exception': 'RuntimeError'}, + ], + [i for i in ipc.get(['context', 'context2'], cmd='launch')]) + + ipc.put(['context', guid1], None, cmd='favorite') + ipc.put(['context', 'context2'], None, cmd='favorite') + self.stop_master() + self.wait_for_events(event='inline', state='offline').wait() + coroutine.sleep(.1) + + self.assertEqual([ + {'activity_id': 'activity_id'}, + {'event': 'launch', 'state': 'init'}, + {'event': 'launch', 'state': 'solve'}, + {'error': 'Not available in offline', 'event': 'failure', 'exception': 'ServiceUnavailable'}, + ], + [i for i in ipc.get(['context', 'context'], cmd='launch')]) + + self.assertEqual([ + {'activity_id': 'activity_id'}, + {'event': 'launch', 'state': 'init'}, + {'event': 'launch', 'state': 'solve'}, + {'error': 'Not available in offline', 'event': 'failure', 'exception': 'ServiceUnavailable'}, + ], + [i for i in ipc.get(['context', guid1], cmd='launch')]) + + self.assertEqual([ + {'activity_id': 'activity_id'}, + {'event': 'launch', 'state': 'init'}, + {'event': 'launch', 'state': 'solve'}, + {'event': 'launch', 'state': 'exec'}, + {'context': 'context2', + 'args': ['false', '-b', 'context2', '-a', 'activity_id'], + 'logs': [ + tests.tmpdir + '/.sugar/default/logs/shell.log', + tests.tmpdir + '/.sugar/default/logs/sugar-network-client.log', + tests.tmpdir + '/.sugar/default/logs/context2_1.log', + ], + 'solution': { + 'context2': { + 'blob': release, + 'command': ['activity', 'false'], + 'content-type': 'application/vnd.olpc-sugar', + 'size': len(activity_bundle), + 'title': 'Activity', + 'unpack_size': len(activity_info), + 'version': [[1], 0], + }, + }, + }, + {'error': 'Process exited with 1 status', 'event': 'failure', 'exception': 'RuntimeError'}, + ], + [i for i in ipc.get(['context', 'context2'], cmd='launch')]) + + def test_SubmitReport(self): + home_volume = self.start_online_client() + ipc = IPCConnection() + + self.touch( + ['file1', 'content1'], + ['file2', 'content2'], + ['file3', 'content3'], + ) + events = [i for i in ipc.post(['report'], {'context': 'context', 'error': 'error', 'logs': [ + tests.tmpdir + '/file1', + tests.tmpdir + '/file2', + tests.tmpdir + '/file3', + ]}, cmd='submit')] + self.assertEqual('done', events[-1]['event']) + guid = events[-1]['guid'] + + self.assertEqual({ + 'context': 'context', + 'error': 'error', + }, + ipc.get(['report', guid], reply=['context', 'error'])) + self.assertEqual(sorted([ + 'content1', + 'content2', + 'content3', + ]), + sorted([ipc.get(['report', guid, 'logs', i]) for i in ipc.get(['report', guid, 'logs']).keys()])) + assert not home_volume['report'][guid].exists + + self.stop_master() + self.wait_for_events(event='inline', state='offline').wait() + + events = [i for i in ipc.post(['report'], {'context': 'context', 'error': 'error', 'logs': [ + tests.tmpdir + '/file1', + tests.tmpdir + '/file2', + tests.tmpdir + '/file3', + ]}, cmd='submit')] + self.assertEqual('done', events[-1]['event']) + guid = events[-1]['guid'] + + self.assertEqual({ + 'context': 'context', + 'error': 'error', + }, + ipc.get(['report', guid], reply=['context', 'error'])) + self.assertEqual(sorted([ + 'content1', + 'content2', + 'content3', + ]), + sorted([ipc.get(['report', guid, 'logs', i]) for i in ipc.get(['report', guid, 'logs']).keys()])) + assert home_volume['report'][guid].exists + + def test_inline(self): + routes._RECONNECT_TIMEOUT = 2 + + this.injector = Injector('client') + cp = ClientRoutes(db.Volume('client', RESOURCES)) + cp.connect(client.api.value) + assert not cp.inline() trigger = self.wait_for_events(cp, event='inline', state='online') - node_volume = self.start_master() - cp._remote_connect() + coroutine.sleep(.5) + self.fork_master() + trigger.wait(.5) + assert trigger.value is None + assert not cp.inline() + trigger.wait() + assert cp.inline() + + trigger = self.wait_for_events(cp, event='inline', state='offline') + self.stop_master() + trigger.wait() + assert not cp.inline() + + def test_DoNotSwitchToOfflineOnRedirectFails(self): + + class Document(db.Resource): + + @db.stored_property(db.Blob) + def blob1(self, value): + raise http.Redirect(prefix + '/blob2') + + @db.stored_property(db.Blob) + def blob2(self, value): + raise http._ConnectionError() + + local_volume = self.start_online_client([User, Document]) + ipc = IPCConnection() + guid = ipc.post(['document'], {}) + prefix = client.api.value + '/document/' + guid + '/' + local_volume['document'].create({'guid': guid}) + + trigger = self.wait_for_events(ipc, event='inline', state='connecting') + try: + ipc.get(['document', guid, 'blob1']) + except Exception: + pass + assert trigger.wait(.1) is None + + trigger = self.wait_for_events(ipc, event='inline', state='connecting') + try: + ipc.get(['document', guid, 'blob2']) + except Exception: + pass + assert trigger.wait(.1) is not None + + def test_FallbackToLocalOnRemoteTransportFails(self): + + class LocalRoutes(routes._LocalRoutes): + + @route('GET', cmd='sleep') + def sleep(self): + return 'local' + + @route('GET', cmd='yield_raw_and_sleep', + mime_type='application/octet-stream') + def yield_raw_and_sleep(self): + yield 'local' + + @route('GET', cmd='yield_json_and_sleep', + mime_type='application/json') + def yield_json_and_sleep(self): + yield '"local"' + + self.override(routes, '_LocalRoutes', LocalRoutes) + this.injector = Injector('client') + home_volume = self.start_client() + ipc = IPCConnection() + + self.assertEqual('local', ipc.get(cmd='sleep')) + self.assertEqual('local', ipc.get(cmd='yield_raw_and_sleep')) + self.assertEqual('local', ipc.get(cmd='yield_json_and_sleep')) + + class NodeRoutes(MasterRoutes): + + @route('GET', cmd='sleep') + def sleep(self): + coroutine.sleep(.5) + return 'remote' + + @route('GET', cmd='yield_raw_and_sleep', + mime_type='application/octet-stream') + def yield_raw_and_sleep(self): + for __ in range(33): + yield "remote\n" + coroutine.sleep(.5) + for __ in range(33): + yield "remote\n" + + @route('GET', cmd='yield_json_and_sleep', + mime_type='application/json') + def yield_json_and_sleep(self): + yield '"' + yield 'r' + coroutine.sleep(1) + yield 'emote"' + + node_pid = self.fork_master([User], NodeRoutes) + self.client_routes._remote_connect() + self.wait_for_events(ipc, event='inline', state='online').wait() + + ts = time.time() + self.assertEqual('remote', ipc.get(cmd='sleep')) + self.assertEqual('remote\n' * 66, ipc.get(cmd='yield_raw_and_sleep')) + self.assertEqual('remote', ipc.get(cmd='yield_json_and_sleep')) + assert time.time() - ts >= 2 + + def kill(): + coroutine.sleep(.5) + self.waitpid(node_pid) + + coroutine.spawn(kill) + self.assertEqual('local', ipc.get(cmd='sleep')) + assert not ipc.get(cmd='inline') + + node_pid = self.fork_master([User], NodeRoutes) + self.client_routes._remote_connect() + self.wait_for_events(ipc, event='inline', state='online').wait() + + coroutine.spawn(kill) + self.assertEqual('local', ipc.get(cmd='yield_raw_and_sleep')) + assert not ipc.get(cmd='inline') + + node_pid = self.fork_master([User], NodeRoutes) + self.client_routes._remote_connect() + self.wait_for_events(ipc, event='inline', state='online').wait() + + coroutine.spawn(kill) + self.assertEqual('local', ipc.get(cmd='yield_json_and_sleep')) + assert not ipc.get(cmd='inline') + + def test_ReconnectOnServerFall(self): + routes._RECONNECT_TIMEOUT = 1 + + this.injector = Injector('client') + node_pid = self.fork_master() + self.start_client() + ipc = IPCConnection() + self.wait_for_events(ipc, event='inline', state='online').wait() + + def shutdown(): + coroutine.sleep(.1) + self.waitpid(node_pid) + coroutine.spawn(shutdown) + self.wait_for_events(ipc, event='inline', state='offline').wait() + + self.fork_master() + self.wait_for_events(ipc, event='inline', state='online').wait() + + def test_SilentReconnectOnGatewayErrors(self): + + class Routes(object): + + subscribe_tries = 0 + + def __init__(self, volume, *args): + pass + + @route('GET', cmd='status', mime_type='application/json') + def info(self): + return {'resources': {}} + + @route('GET', cmd='subscribe', mime_type='text/event-stream') + def subscribe(self, request=None, response=None, **condition): + Routes.subscribe_tries += 1 + coroutine.sleep(.1) + if Routes.subscribe_tries % 2: + raise http.BadGateway() + else: + raise http.GatewayTimeout() + + this.injector = Injector('client') + node_pid = self.start_master(None, Routes) + self.start_client() + ipc = IPCConnection() + self.wait_for_events(ipc, event='inline', state='online').wait() + + def read_events(): + for event in ipc.subscribe(): + events.append(event) + events = [] + coroutine.spawn(read_events) + + coroutine.sleep(1) + self.assertEqual([{'event': 'pong'}], events) + assert Routes.subscribe_tries > 2 + - guid = call(cp, post) - self.assertEqual([], call(cp, Request(method='GET', path=['context', guid, 'layer']))) - def test_CachedClientRoutes(self): - volume = db.Volume('client', model.RESOURCES, lazy_open=True) + + + + + + + def ___test_CachedClientRoutes(self): + volume = db.Volume('client', RESOURCES, lazy_open=True) cp = CachedClientRoutes(volume, client.api.value) post = Request(method='POST', path=['context']) @@ -214,8 +1167,8 @@ class RoutesTest(tests.Test): {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, self.node_volume['context'].get(guid2)['author']) - def test_CachedClientRoutes_WipeReports(self): - volume = db.Volume('client', model.RESOURCES, lazy_open=True) + def ___test_CachedClientRoutes_WipeReports(self): + volume = db.Volume('client', RESOURCES, lazy_open=True) cp = CachedClientRoutes(volume, client.api.value) post = Request(method='POST', path=['report']) @@ -227,15 +1180,15 @@ class RoutesTest(tests.Test): guid = call(cp, post) trigger = self.wait_for_events(cp, event='push') - self.start_master([User, Report]) + self.start_master() cp._remote_connect() trigger.wait() assert not volume['report'].exists(guid) assert self.node_volume['report'].exists(guid) - def test_CachedClientRoutes_OpenOnlyChangedResources(self): - volume = db.Volume('client', model.RESOURCES, lazy_open=True) + def ___test_CachedClientRoutes_OpenOnlyChangedResources(self): + volume = db.Volume('client', RESOURCES, lazy_open=True) cp = CachedClientRoutes(volume, client.api.value) guid = call(cp, Request(method='POST', path=['context'], content_type='application/json', content={ 'type': 'activity', @@ -246,7 +1199,7 @@ class RoutesTest(tests.Test): })) cp.close() - volume = db.Volume('client', model.RESOURCES, lazy_open=True) + volume = db.Volume('client', RESOURCES, lazy_open=True) cp = CachedClientRoutes(volume, client.api.value) trigger = self.wait_for_events(cp, event='push') @@ -258,8 +1211,8 @@ class RoutesTest(tests.Test): assert self.node_volume['context'].exists(guid) self.assertEqual(['context'], volume.keys()) - def test_SwitchToOfflineForAbsentOnlineProps(self): - volume = db.Volume('client', model.RESOURCES) + def ___test_SwitchToOfflineForAbsentOnlineProps(self): + volume = db.Volume('client', RESOURCES) cp = ClientRoutes(volume, client.api.value) post = Request(method='POST', path=['context']) @@ -282,177 +1235,6 @@ class RoutesTest(tests.Test): assert not self.node_volume['context'].exists(guid) self.assertEqual('title', call(cp, Request(method='GET', path=['context', guid, 'title']))) - def test_I18nQuery(self): - os.environ['LANGUAGE'] = 'foo' - self.start_online_client() - ipc = IPCConnection() - - guid1 = self.node_volume['context'].create({ - 'type': 'activity', - 'title': {'en-US': 'qwe', 'ru-RU': 'йцу'}, - 'summary': 'summary', - 'description': 'description', - }) - guid2 = self.node_volume['context'].create({ - 'type': 'activity', - 'title': {'en-US': 'qwerty', 'ru-RU': 'йцукен'}, - 'summary': 'summary', - 'description': 'description', - }) - - self.assertEqual([ - {'guid': guid1}, - {'guid': guid2}, - ], - ipc.get(['context'], query='йцу')['result']) - self.assertEqual([ - {'guid': guid1}, - {'guid': guid2}, - ], - ipc.get(['context'], query='qwe')['result']) - - self.assertEqual([ - {'guid': guid2}, - ], - ipc.get(['context'], query='йцукен')['result']) - self.assertEqual([ - {'guid': guid2}, - ], - ipc.get(['context'], query='qwerty')['result']) - - def test_IgnoreClonesOnOpen(self): - self.start_online_client() - ipc = IPCConnection() - - guid = ipc.upload(['release'], StringIO(self.zips(['TestActivity/activity/activity.info', [ - '[Activity]', - 'name = name', - 'bundle_id = context', - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license = Public Domain', - 'stability = stable', - ]])), cmd='submit', initial=True) - ipc.put(['context', 'context'], True, cmd='clone') - ts = time.time() - os.utime('client/release/%s/%s' % (guid[:2], guid), (ts - 2 * 86400, ts - 2 * 86400)) - self.client_routes.close() - self.stop_nodes() - - home_volume = self.start_online_client() - cache_lifetime.value = 1 - self.client_routes.recycle() - assert home_volume['release'].exists(guid) - assert exists('client/release/%s/%s' % (guid[:2], guid)) - - def test_IgnoreClonesWhileCheckingFreeSpace(self): - home_volume = self.start_online_client() - ipc = IPCConnection() - - guid = ipc.upload(['release'], StringIO(self.zips(['TestActivity/activity/activity.info', [ - '[Activity]', - 'name = name', - 'bundle_id = context', - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license = Public Domain', - 'stability = stable', - ]])), cmd='submit', initial=True) - ipc.put(['context', 'context'], True, cmd='clone') - - class statvfs(object): - f_blocks = 100 - f_bfree = 10 - f_frsize = 1 - - self.override(os, 'statvfs', lambda *args: statvfs()) - cache_limit.value = 10 - - self.assertRaises(RuntimeError, self.client_routes._cache.ensure, 1, 0) - assert home_volume['release'].exists(guid) - assert exists('client/release/%s/%s' % (guid[:2], guid)) - - def test_IgnoreClonesOnRecycle(self): - home_volume = self.start_online_client() - ipc = IPCConnection() - - guid = ipc.upload(['release'], StringIO(self.zips(['TestActivity/activity/activity.info', [ - '[Activity]', - 'name = name', - 'bundle_id = context', - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license = Public Domain', - 'stability = stable', - ]])), cmd='submit', initial=True) - ipc.put(['context', 'context'], True, cmd='clone') - ts = time.time() - os.utime('client/release/%s/%s' % (guid[:2], guid), (ts - 2 * 86400, ts - 2 * 86400)) - - cache_lifetime.value = 1 - self.client_routes.recycle() - assert home_volume['release'].exists(guid) - assert exists('client/release/%s/%s' % (guid[:2], guid)) - - def test_LanguagesFallbackInRequests(self): - self.start_online_client() - ipc = IPCConnection() - - guid1 = self.node_volume['context'].create({ - 'type': 'activity', - 'title': {'en': '1', 'ru': '2', 'es': '3'}, - 'summary': '', - 'description': '', - }) - guid2 = self.node_volume['context'].create({ - 'type': 'activity', - 'title': {'en': '1', 'ru': '2'}, - 'summary': '', - 'description': '', - }) - guid3 = self.node_volume['context'].create({ - 'type': 'activity', - 'title': {'en': '1'}, - 'summary': '', - 'description': '', - }) - - i18n._default_langs = None - os.environ['LANGUAGE'] = 'es:ru:en' - ipc = IPCConnection() - self.assertEqual('3', ipc.get(['context', guid1, 'title'])) - self.assertEqual('2', ipc.get(['context', guid2, 'title'])) - self.assertEqual('1', ipc.get(['context', guid3, 'title'])) - - i18n._default_langs = None - os.environ['LANGUAGE'] = 'ru:en' - ipc = IPCConnection() - self.assertEqual('2', ipc.get(['context', guid1, 'title'])) - self.assertEqual('2', ipc.get(['context', guid2, 'title'])) - self.assertEqual('1', ipc.get(['context', guid3, 'title'])) - - i18n._default_langs = None - os.environ['LANGUAGE'] = 'en' - ipc = IPCConnection() - self.assertEqual('1', ipc.get(['context', guid1, 'title'])) - self.assertEqual('1', ipc.get(['context', guid2, 'title'])) - self.assertEqual('1', ipc.get(['context', guid3, 'title'])) - - i18n._default_langs = None - os.environ['LANGUAGE'] = 'foo' - ipc = IPCConnection() - self.assertEqual('1', ipc.get(['context', guid1, 'title'])) - self.assertEqual('1', ipc.get(['context', guid2, 'title'])) - self.assertEqual('1', ipc.get(['context', guid3, 'title'])) - - -def call(routes, request): - router = Router(routes) - return router.call(request, Response()) - if __name__ == '__main__': tests.main() diff --git a/tests/units/client/server_routes.py b/tests/units/client/server_routes.py deleted file mode 100755 index 8c72468..0000000 --- a/tests/units/client/server_routes.py +++ /dev/null @@ -1,226 +0,0 @@ -#!/usr/bin/env python -# sugar-lint: disable - -import os -import shutil -import hashlib -from os.path import exists - -from __init__ import tests, src_root - -from sugar_network import db, client, model -from sugar_network.client import IPCConnection -from sugar_network.client.routes import ClientRoutes -from sugar_network.db import Volume -from sugar_network.toolkit.router import Router -from sugar_network.toolkit import mountpoints, coroutine - - -class ServerRoutesTest(tests.Test): - - def test_whoami(self): - self.start_node() - ipc = IPCConnection() - - self.assertEqual( - {'guid': tests.UID, 'roles': [], 'route': 'proxy'}, - ipc.get(cmd='whoami')) - - def test_Events(self): - self.start_node() - ipc = IPCConnection() - events = [] - - def read_events(): - for event in ipc.subscribe(event='!commit'): - events.append(event) - coroutine.spawn(read_events) - coroutine.dispatch() - - guid = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - ipc.put(['context', guid], { - 'title': 'title_2', - }) - coroutine.sleep(.1) - ipc.delete(['context', guid]) - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, - ], - events) - del events[:] - - guid = self.node_volume['context'].create({ - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - self.node_volume['context'].update(guid, { - 'title': 'title_2', - }) - coroutine.sleep(.1) - self.node_volume['context'].delete(guid) - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, - ], - events) - del events[:] - - guid = self.home_volume['context'].create({ - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - self.home_volume['context'].update(guid, { - 'title': 'title_2', - }) - coroutine.sleep(.1) - self.home_volume['context'].delete(guid) - coroutine.sleep(.1) - - self.assertEqual([], events) - return - - self.node.stop() - coroutine.sleep(.1) - del events[:] - - guid = self.home_volume['context'].create({ - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - self.home_volume['context'].update(guid, { - 'title': 'title_2', - }) - coroutine.sleep(.1) - self.home_volume['context'].delete(guid) - coroutine.sleep(.1) - - self.assertEqual([ - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, - ], - events) - del events[:] - - def test_BLOBs(self): - self.start_node() - ipc = IPCConnection() - - guid = ipc.post(['context'], { - 'type': 'package', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - blob = 'logo_blob' - ipc.request('PUT', ['context', guid, 'logo'], blob) - - self.assertEqual( - blob, - ipc.request('GET', ['context', guid, 'logo']).content) - self.assertEqual({ - 'logo': { - 'url': 'http://127.0.0.1:5555/context/%s/logo' % guid, - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'image/png', - }, - }, - ipc.get(['context', guid], reply=['logo'])) - self.assertEqual([{ - 'logo': { - 'url': 'http://127.0.0.1:5555/context/%s/logo' % guid, - 'blob_size': len(blob), - 'digest': hashlib.sha1(blob).hexdigest(), - 'mime_type': 'image/png', - }, - }], - ipc.get(['context'], reply=['logo'])['result']) - - self.assertEqual( - file(src_root + '/sugar_network/static/httpdocs/images/package.png').read(), - ipc.request('GET', ['context', guid, 'icon']).content) - self.assertEqual({ - 'icon': { - 'url': 'http://127.0.0.1:5555/static/images/package.png', - 'mime_type': 'image/png', - }, - }, - ipc.get(['context', guid], reply=['icon'])) - self.assertEqual([{ - 'icon': { - 'url': 'http://127.0.0.1:5555/static/images/package.png', - 'mime_type': 'image/png', - }, - }], - ipc.get(['context'], reply=['icon'])['result']) - - def test_PopulateNode(self): - os.makedirs('disk/sugar-network') - volume = Volume('db', model.RESOURCES) - cp = ClientRoutes(volume) - - assert not cp.inline() - trigger = self.wait_for_events(cp, event='inline', state='online') - mountpoints.populate('.') - coroutine.dispatch() - assert trigger.value is not None - assert cp.inline() - - def test_MountNode(self): - volume = Volume('db', model.RESOURCES) - cp = ClientRoutes(volume) - - trigger = self.wait_for_events(cp, event='inline', state='online') - mountpoints.populate('.') - assert not cp.inline() - assert trigger.value is None - - coroutine.spawn(mountpoints.monitor, '.') - coroutine.dispatch() - os.makedirs('disk/sugar-network') - trigger.wait() - assert cp.inline() - - def test_UnmountNode(self): - cp = self.start_node() - assert cp.inline() - trigger = self.wait_for_events(cp, event='inline', state='offline') - shutil.rmtree('disk') - trigger.wait() - assert not cp.inline() - - def start_node(self): - os.makedirs('disk/sugar-network') - self.home_volume = Volume('db', model.RESOURCES) - cp = ClientRoutes(self.home_volume) - trigger = self.wait_for_events(cp, event='inline', state='online') - coroutine.spawn(mountpoints.monitor, tests.tmpdir) - trigger.wait() - self.node_volume = cp._node.volume - server = coroutine.WSGIServer(('127.0.0.1', client.ipc_port.value), Router(cp)) - coroutine.spawn(server.serve_forever) - coroutine.dispatch() - return cp - - -if __name__ == '__main__': - tests.main() diff --git a/tests/units/db/resource.py b/tests/units/db/resource.py index 64187aa..05aaddf 100755 --- a/tests/units/db/resource.py +++ b/tests/units/db/resource.py @@ -31,6 +31,7 @@ class ResourceTest(tests.Test): def setUp(self, fork_num=0): tests.Test.setUp(self, fork_num) + this.localcast = lambda x: x this.broadcast = lambda x: x def test_ActiveProperty_Slotted(self): @@ -45,7 +46,7 @@ class ResourceTest(tests.Test): def not_slotted(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) self.assertEqual(1, directory.metadata['slotted'].slot) directory.create({'slotted': 'slotted', 'not_slotted': 'not_slotted'}) @@ -70,7 +71,7 @@ class ResourceTest(tests.Test): def prop_2(self, value): return value - self.assertRaises(RuntimeError, Directory, tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + self.assertRaises(RuntimeError, Directory, tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) def test_ActiveProperty_Terms(self): @@ -84,7 +85,7 @@ class ResourceTest(tests.Test): def not_term(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) self.assertEqual('T', directory.metadata['term'].prefix) guid = directory.create({'term': 'term', 'not_term': 'not_term'}) @@ -110,7 +111,7 @@ class ResourceTest(tests.Test): def prop_2(self, value): return value - self.assertRaises(RuntimeError, Directory, tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + self.assertRaises(RuntimeError, Directory, tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) def test_ActiveProperty_FullTextSearch(self): @@ -124,7 +125,7 @@ class ResourceTest(tests.Test): def yes(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) self.assertEqual(False, directory.metadata['no'].full_text) self.assertEqual(True, directory.metadata['yes'].full_text) @@ -145,7 +146,7 @@ class ResourceTest(tests.Test): def prop_2(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid = directory.create({'prop_1': '1', 'prop_2': '2'}) self.assertEqual( @@ -165,7 +166,7 @@ class ResourceTest(tests.Test): def prop(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid_1 = directory.create({'prop': '1'}) guid_2 = directory.create({'prop': '2'}) @@ -212,7 +213,7 @@ class ResourceTest(tests.Test): ('db/document/2/2/seqno', '{"value": 0}'), ) - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) self.assertEqual(0, directory._index.mtime) for i in directory.populate(): @@ -264,7 +265,7 @@ class ResourceTest(tests.Test): ('db/document/3/3/seqno', ''), ) - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) populated = 0 for i in directory.populate(): @@ -285,7 +286,7 @@ class ResourceTest(tests.Test): def prop(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid = directory.create({'guid': 'guid', 'prop': 'foo'}) self.assertEqual( @@ -305,7 +306,7 @@ class ResourceTest(tests.Test): def prop(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid_1 = directory.create({'prop': 'value'}) seqno = directory.get(guid_1).get('seqno') @@ -349,7 +350,7 @@ class ResourceTest(tests.Test): def prop2(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid = directory.create({'guid': '1', 'prop1': '1', 'prop2': '2'}) doc = directory.get(guid) @@ -366,7 +367,7 @@ class ResourceTest(tests.Test): def prop(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid = directory.create({'guid': '1', 'prop': {'ru': 'ru'}}) doc = directory.get(guid) @@ -384,7 +385,7 @@ class ResourceTest(tests.Test): def prop(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid = directory.create({'prop': '1'}) self.assertEqual([guid], [i.guid for i in directory.find()[0]]) directory.commit() @@ -417,7 +418,7 @@ class ResourceTest(tests.Test): def prop3(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid = directory.create({'guid': 'guid', 'prop1': 'set1', 'prop2': 'set2', 'prop3': 'set3'}) doc = directory.get(guid) @@ -446,7 +447,7 @@ class ResourceTest(tests.Test): def prop3(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid = directory.create({'guid': 'guid', 'prop2': 'set2'}) doc = directory.get(guid) @@ -475,7 +476,7 @@ class ResourceTest(tests.Test): def prop3(self, value): return value - directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno()) + directory = Directory(tests.tmpdir, Document, IndexWriter, _SessionSeqno(), this.broadcast) guid = directory.create({'guid': 'guid', 'prop2': 'set2'}) doc = directory.get(guid) diff --git a/tests/units/db/routes.py b/tests/units/db/routes.py index 9f9131e..4fa4cef 100755 --- a/tests/units/db/routes.py +++ b/tests/units/db/routes.py @@ -24,6 +24,10 @@ from sugar_network.toolkit import coroutine, http, i18n class RoutesTest(tests.Test): + def setUp(self, fork_num=0): + tests.Test.setUp(self, fork_num) + this.localcast = lambda x: x + def test_PostDefaults(self): class Document(db.Resource): @@ -646,12 +650,6 @@ class RoutesTest(tests.Test): def test_on_create_Override(self): - class Routes(db.Routes): - - def on_create(self, request, props): - props['prop'] = 'overriden' - db.Routes.on_create(self, request, props) - class TestDocument(db.Resource): @db.indexed_property(slot=1, default='') @@ -662,13 +660,16 @@ class RoutesTest(tests.Test): def localized_prop(self, value): return value + def created(self): + self.posts['prop'] = 'overriden' + volume = db.Volume(tests.tmpdir, [TestDocument]) - router = Router(Routes(volume)) + router = Router(db.Routes(volume)) - guid = this.call(method='POST', path=['testdocument'], content={'prop': 'foo'}, routes=Routes) + guid = this.call(method='POST', path=['testdocument'], content={'prop': 'foo'}) self.assertEqual('overriden', volume['testdocument'].get(guid)['prop']) - this.call(method='PUT', path=['testdocument', guid], content={'prop': 'bar'}, routes=Routes) + this.call(method='PUT', path=['testdocument', guid], content={'prop': 'bar'}) self.assertEqual('bar', volume['testdocument'].get(guid)['prop']) def test_on_update(self): @@ -696,12 +697,6 @@ class RoutesTest(tests.Test): def test_on_update_Override(self): - class Routes(db.Routes): - - def on_update(self, request, props): - props['prop'] = 'overriden' - db.Routes.on_update(self, request, props) - class TestDocument(db.Resource): @db.indexed_property(slot=1, default='') @@ -712,13 +707,16 @@ class RoutesTest(tests.Test): def localized_prop(self, value): return value + def updated(self): + self.posts['prop'] = 'overriden' + volume = db.Volume(tests.tmpdir, [TestDocument]) - router = Router(Routes(volume)) + router = Router(db.Routes(volume)) - guid = this.call(method='POST', path=['testdocument'], content={'prop': 'foo'}, routes=Routes) + guid = this.call(method='POST', path=['testdocument'], content={'prop': 'foo'}) self.assertEqual('foo', volume['testdocument'].get(guid)['prop']) - this.call(method='PUT', path=['testdocument', guid], content={'prop': 'bar'}, routes=Routes) + this.call(method='PUT', path=['testdocument', guid], content={'prop': 'bar'}) self.assertEqual('overriden', volume['testdocument'].get(guid)['prop']) def __test_DoNotPassGuidsForCreate(self): @@ -796,6 +794,7 @@ class RoutesTest(tests.Test): ) events = [] + this.localcast = lambda x: events.append(x) this.broadcast = lambda x: events.append(x) volume = db.Volume(tests.tmpdir, [Document1, Document2]) volume['document1'] @@ -821,8 +820,8 @@ class RoutesTest(tests.Test): volume['document1'].update('guid1', {'prop': 'foo'}) volume['document2'].update('guid2', {'prop': 'bar'}) self.assertEqual([ - {'event': 'update', 'resource': 'document1', 'guid': 'guid1'}, - {'event': 'update', 'resource': 'document2', 'guid': 'guid2'}, + {'event': 'update', 'resource': 'document1', 'guid': 'guid1', 'props': {'prop': 'foo'}}, + {'event': 'update', 'resource': 'document2', 'guid': 'guid2', 'props': {'prop': 'bar'}}, ], events) del events[:] @@ -1516,7 +1515,7 @@ class RoutesTest(tests.Test): events = [] volume = db.Volume(tests.tmpdir, [Document]) router = Router(db.Routes(volume)) - this.broadcast = lambda x: events.append(x) + this.localcast = lambda x: events.append(x) guid = this.call(method='POST', path=['document'], content={}) self.assertRaises(http.NotFound, this.call, method='POST', path=['document', 'foo', 'bar'], content={}) @@ -1532,7 +1531,10 @@ class RoutesTest(tests.Test): }, volume['document'].get(guid)['prop3']) self.assertEqual([ - {'event': 'update', 'resource': 'document', 'guid': guid}, + {'event': 'update', 'resource': 'document', 'guid': guid, 'props': { + 'mtime': 0, + 'prop3': {'0': {'seqno': 2, 'value': 0}}, + }}, ], events) @@ -1556,6 +1558,7 @@ class RoutesTest(tests.Test): volume['document'].get(guid)['prop3']) def test_RemoveAggprops(self): + self.override(time, 'time', lambda: 0) class Document(db.Resource): @@ -1570,7 +1573,7 @@ class RoutesTest(tests.Test): events = [] volume = db.Volume(tests.tmpdir, [Document]) router = Router(db.Routes(volume)) - this.broadcast = lambda x: events.append(x) + this.localcast = lambda x: events.append(x) guid = this.call(method='POST', path=['document'], content={}) agg_guid = this.call(method='POST', path=['document', guid, 'prop1'], content=2) @@ -1594,7 +1597,10 @@ class RoutesTest(tests.Test): {agg_guid: {'seqno': 4}}, volume['document'].get(guid)['prop2']) self.assertEqual([ - {'event': 'update', 'resource': 'document', 'guid': guid}, + {'event': 'update', 'resource': 'document', 'guid': guid, 'props': { + 'mtime': 0, + 'prop2': {agg_guid: {'seqno': 4}}, + }}, ], events) @@ -1609,7 +1615,7 @@ class RoutesTest(tests.Test): events = [] volume = db.Volume(tests.tmpdir, [Document]) router = Router(db.Routes(volume)) - this.broadcast = lambda x: events.append(x) + this.localcast = lambda x: events.append(x) guid = this.call(method='POST', path=['document'], content={}) del events[:] @@ -1617,6 +1623,7 @@ class RoutesTest(tests.Test): self.assertEqual([], events) def test_UpdateAggprops(self): + self.override(time, 'time', lambda: 0) class Document(db.Resource): @@ -1631,7 +1638,7 @@ class RoutesTest(tests.Test): events = [] volume = db.Volume(tests.tmpdir, [Document]) router = Router(db.Routes(volume)) - this.broadcast = lambda x: events.append(x) + this.localcast = lambda x: events.append(x) guid = this.call(method='POST', path=['document'], content={}) agg_guid = this.call(method='POST', path=['document', guid, 'prop1'], content=1) @@ -1655,11 +1662,15 @@ class RoutesTest(tests.Test): {agg_guid: {'seqno': 4, 'value': 3}}, volume['document'].get(guid)['prop2']) self.assertEqual([ - {'event': 'update', 'resource': 'document', 'guid': guid}, + {'event': 'update', 'resource': 'document', 'guid': guid, 'props': { + 'mtime': 0, + 'prop2': {agg_guid: {'seqno': 4, 'value': 3}}, + }}, ], events) def test_PostAbsentAggpropsOnUpdate(self): + self.override(time, 'time', lambda: 0) class Document(db.Resource): @@ -1670,7 +1681,7 @@ class RoutesTest(tests.Test): events = [] volume = db.Volume(tests.tmpdir, [Document]) router = Router(db.Routes(volume)) - this.broadcast = lambda x: events.append(x) + this.localcast = lambda x: events.append(x) guid = this.call(method='POST', path=['document'], content={}) del events[:] @@ -1679,7 +1690,10 @@ class RoutesTest(tests.Test): {'absent': {'seqno': 2, 'value': 'probe'}}, volume['document'].get(guid)['prop']) self.assertEqual([ - {'event': 'update', 'resource': 'document', 'guid': guid}, + {'event': 'update', 'resource': 'document', 'guid': guid, 'props': { + 'mtime': 0, + 'prop': {'absent': {'seqno': 2, 'value': 'probe'}}, + }}, ], events) @@ -1824,6 +1838,27 @@ class RoutesTest(tests.Test): sorted([guid2, guid3]), sorted([i['guid'] for i in this.call(method='GET', path=['document'], query='comments:c')['result']])) + def test_HandleDeletes(self): + + class Document(db.Resource): + pass + + volume = db.Volume(tests.tmpdir, [Document]) + router = Router(db.Routes(volume)) + + guid = this.call(method='POST', path=['document'], content={}) + self.assertEqual('active', volume['document'][guid]['state']) + + events = [] + this.localcast = lambda x: events.append(x) + this.call(method='DELETE', path=['document', guid], principal=tests.UID) + + self.assertRaises(http.NotFound, this.call, method='GET', path=['document', guid]) + self.assertEqual('deleted', volume['document'][guid]['state']) + self.assertEqual( + [{'event': 'delete', 'resource': 'document', 'guid': guid}], + events) + if __name__ == '__main__': tests.main() diff --git a/tests/units/db/volume.py b/tests/units/db/volume.py index 3b10d7e..b5f01a7 100755 --- a/tests/units/db/volume.py +++ b/tests/units/db/volume.py @@ -31,7 +31,7 @@ class VolumeTest(tests.Test): def setUp(self, fork_num=0): tests.Test.setUp(self, fork_num) - this.broadcast = lambda x: x + this.localcast = lambda x: x def test_diff(self): @@ -323,6 +323,51 @@ class VolumeTest(tests.Test): self.assertRaises(StopIteration, patch.next) self.assertEqual([[4, None]], r) + def test_clone(self): + + class Document(db.Resource): + + @db.stored_property() + def prop1(self, value): + return value + + @db.stored_property() + def prop2(self, value): + return value + + @db.stored_property(db.Blob) + def prop3(self, value): + return value + + @db.stored_property(db.Blob) + def prop4(self, value): + return value + + volume = db.Volume('.', [Document]) + + volume['document'].create({ + 'guid': 'guid', + 'prop1': '1', + 'prop2': 2, + 'prop3': volume.blobs.post('333', '3/3').digest, + 'prop4': volume.blobs.post('4444', '4/4').digest, + }) + self.utime('db/document/gu/guid', 1) + + self.assertEqual([ + {'content-type': '3/3', 'content-length': '3', 'x-seqno': '1'}, + {'content-type': '4/4', 'content-length': '4', 'x-seqno': '2'}, + {'resource': 'document'}, + {'guid': 'guid', 'patch': { + 'guid': {'value': 'guid', 'mtime': 1}, + 'prop1': {'value': '1', 'mtime': 1}, + 'prop2': {'value': 2, 'mtime': 1}, + 'prop3': {'value': hashlib.sha1('333').hexdigest(), 'mtime': 1}, + 'prop4': {'value': hashlib.sha1('4444').hexdigest(), 'mtime': 1}, + }}, + ], + [dict(i) for i in volume.clone('document', 'guid')]) + def test_patch_New(self): class Document(db.Resource): @@ -735,7 +780,7 @@ class VolumeTest(tests.Test): def prop(self, value): return value + 1 - directory = Directory('document', Document, IndexWriter, _SessionSeqno()) + directory = Directory('document', Document, IndexWriter, _SessionSeqno(), this.localcast) directory.patch('1', { 'guid': {'mtime': 1, 'value': '1'}, @@ -772,7 +817,7 @@ class VolumeTest(tests.Test): patch = generator() self.assertEqual((101, [[1, 3]]), volume.patch(patch)) - assert volume['document'].exists('1') + assert volume['document']['1'].exists class _SessionSeqno(object): diff --git a/tests/units/model/context.py b/tests/units/model/context.py index bd39c04..45a1ce8 100755 --- a/tests/units/model/context.py +++ b/tests/units/model/context.py @@ -96,20 +96,20 @@ class ContextTest(tests.Test): assert release1 == str(hashlib.sha1(bundle1).hexdigest()) self.assertEqual({ release1: { - 'seqno': 5, + 'seqno': 10, 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, 'value': { 'license': ['Public Domain'], 'announce': next(volume['post'].find(query='title:1')[0]).guid, 'version': [[1], 0], 'requires': {}, - 'command': 'true', + 'commands': {'activity': {'exec': 'true'}}, 'bundles': {'*-*': {'blob': str(hashlib.sha1(bundle1).hexdigest()), 'unpack_size': len(activity_info1)}}, 'stability': 'stable', }, }, }, conn.get(['context', context, 'releases'])) - assert blobs.get(str(hashlib.sha1(bundle1).hexdigest())) + assert volume.blobs.get(str(hashlib.sha1(bundle1).hexdigest())).exists activity_info2 = '\n'.join([ '[Activity]', @@ -125,71 +125,71 @@ class ContextTest(tests.Test): assert release2 == str(hashlib.sha1(bundle2).hexdigest()) self.assertEqual({ release1: { - 'seqno': 5, + 'seqno': 10, 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, 'value': { 'license': ['Public Domain'], 'announce': next(volume['post'].find(query='title:1')[0]).guid, 'version': [[1], 0], 'requires': {}, - 'command': 'true', + 'commands': {'activity': {'exec': 'true'}}, 'bundles': {'*-*': {'blob': str(hashlib.sha1(bundle1).hexdigest()), 'unpack_size': len(activity_info1)}}, 'stability': 'stable', }, }, release2: { - 'seqno': 7, + 'seqno': 13, 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, 'value': { 'license': ['Public Domain'], 'announce': next(volume['post'].find(query='title:2')[0]).guid, 'version': [[2], 0], 'requires': {}, - 'command': 'true', + 'commands': {'activity': {'exec': 'true'}}, 'bundles': {'*-*': {'blob': str(hashlib.sha1(bundle2).hexdigest()), 'unpack_size': len(activity_info2)}}, 'stability': 'stable', }, }, }, conn.get(['context', context, 'releases'])) - assert blobs.get(str(hashlib.sha1(bundle1).hexdigest())) - assert blobs.get(str(hashlib.sha1(bundle2).hexdigest())) + assert volume.blobs.get(str(hashlib.sha1(bundle1).hexdigest())).exists + assert volume.blobs.get(str(hashlib.sha1(bundle2).hexdigest())).exists conn.delete(['context', context, 'releases', release1]) self.assertEqual({ release1: { - 'seqno': 8, + 'seqno': 15, 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, }, release2: { - 'seqno': 7, + 'seqno': 13, 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, 'value': { 'license': ['Public Domain'], 'announce': next(volume['post'].find(query='title:2')[0]).guid, 'version': [[2], 0], 'requires': {}, - 'command': 'true', + 'commands': {'activity': {'exec': 'true'}}, 'bundles': {'*-*': {'blob': str(hashlib.sha1(bundle2).hexdigest()), 'unpack_size': len(activity_info2)}}, 'stability': 'stable', }, }, }, conn.get(['context', context, 'releases'])) - assert blobs.get(str(hashlib.sha1(bundle1).hexdigest())) is None - assert blobs.get(str(hashlib.sha1(bundle2).hexdigest())) + assert not volume.blobs.get(str(hashlib.sha1(bundle1).hexdigest())).exists + assert volume.blobs.get(str(hashlib.sha1(bundle2).hexdigest())).exists conn.delete(['context', context, 'releases', release2]) self.assertEqual({ release1: { - 'seqno': 8, + 'seqno': 15, 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, }, release2: { - 'seqno': 9, + 'seqno': 17, 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, }, }, conn.get(['context', context, 'releases'])) - assert blobs.get(str(hashlib.sha1(bundle1).hexdigest())) is None - assert blobs.get(str(hashlib.sha1(bundle2).hexdigest())) is None + assert not volume.blobs.get(str(hashlib.sha1(bundle1).hexdigest())).exists + assert not volume.blobs.get(str(hashlib.sha1(bundle2).hexdigest())).exists def test_IncrementReleasesSeqnoOnNewReleases(self): events = [] @@ -287,13 +287,29 @@ class ContextTest(tests.Test): ], [i for i in events if i['event'] == 'release']) self.assertEqual(0, volume.releases_seqno.value) + bundle = self.zips(('topdir/activity/activity.info', '\n'.join([ + '[Activity]', + 'name = Activity', + 'bundle_id = %s' % context, + 'exec = true', + 'icon = icon', + 'activity_version = 2', + 'license = Public Domain', + ]))) + release = conn.upload(['context', context, 'releases'], StringIO(bundle)) + self.assertEqual([ + {'seqno': 1, 'event': 'release'} + ], [i for i in events if i['event'] == 'release']) + self.assertEqual(1, volume.releases_seqno.value) + del events[:] + conn.put(['context', context], { 'dependencies': 'dep', }) self.assertEqual([ - {'event': 'release', 'seqno': 1}, + {'event': 'release', 'seqno': 2}, ], [i for i in events if i['event'] == 'release']) - self.assertEqual(1, volume.releases_seqno.value) + self.assertEqual(2, volume.releases_seqno.value) def test_IncrementReleasesSeqnoOnDeletes(self): events = [] @@ -311,22 +327,28 @@ class ContextTest(tests.Test): ], [i for i in events if i['event'] == 'release']) self.assertEqual(0, volume.releases_seqno.value) - conn.put(['context', context], { - 'layer': ['deleted'], - }) + bundle = self.zips(('topdir/activity/activity.info', '\n'.join([ + '[Activity]', + 'name = Activity', + 'bundle_id = %s' % context, + 'exec = true', + 'icon = icon', + 'activity_version = 2', + 'license = Public Domain', + ]))) + release = conn.upload(['context', context, 'releases'], StringIO(bundle)) self.assertEqual([ - {'event': 'release', 'seqno': 1}, + {'seqno': 1, 'event': 'release'} ], [i for i in events if i['event'] == 'release']) self.assertEqual(1, volume.releases_seqno.value) + del events[:] - conn.put(['context', context], { - 'layer': [], - }) + conn.delete(['context', context]) self.assertEqual([ - {'event': 'release', 'seqno': 1}, {'event': 'release', 'seqno': 2}, ], [i for i in events if i['event'] == 'release']) self.assertEqual(2, volume.releases_seqno.value) + del events[:] def test_RestoreReleasesSeqno(self): events = [] @@ -341,6 +363,16 @@ class ContextTest(tests.Test): 'description': 'description', 'dependencies': 'dep', }) + bundle = self.zips(('topdir/activity/activity.info', '\n'.join([ + '[Activity]', + 'name = Activity', + 'bundle_id = %s' % context, + 'exec = true', + 'icon = icon', + 'activity_version = 2', + 'license = Public Domain', + ]))) + release = conn.upload(['context', context, 'releases'], StringIO(bundle)) self.assertEqual(1, volume.releases_seqno.value) volume.close() diff --git a/tests/units/model/model.py b/tests/units/model/model.py index 49fd1a3..857a54b 100755 --- a/tests/units/model/model.py +++ b/tests/units/model/model.py @@ -10,6 +10,8 @@ from __init__ import tests from sugar_network import db from sugar_network.model import load_bundle from sugar_network.model.post import Post +from sugar_network.model.context import Context +from sugar_network.node.model import User from sugar_network.client import IPCConnection, Connection, keyfile from sugar_network.toolkit.router import Request from sugar_network.toolkit.coroutine import this @@ -19,6 +21,7 @@ from sugar_network.toolkit import i18n, http, coroutine, enforce class ModelTest(tests.Test): def test_RatingSort(self): + this.localcast = lambda event: None directory = db.Volume('db', [Post])['post'] directory.create({'guid': '1', 'context': '', 'type': 'post', 'title': {}, 'message': {}, 'rating': [0, 0]}) @@ -518,7 +521,7 @@ class ModelTest(tests.Test): release['requires']) def test_load_bundle_IgnoreNotSupportedContextTypes(self): - volume = self.start_master() + volume = self.start_master([User, Context]) conn = Connection(auth=http.SugarAuth(keyfile.value)) context = conn.post(['context'], { @@ -528,9 +531,9 @@ class ModelTest(tests.Test): 'description': '', }) this.request = Request(method='POST', path=['context', context]) - aggid = conn.post(['context', context, 'releases'], -1) + aggid = conn.post(['context', context, 'releases'], {}) self.assertEqual({ - aggid: {'seqno': 4, 'value': -1, 'author': {tests.UID: {'role': 3, 'name': tests.UID, 'order': 0}}}, + aggid: {'seqno': 4, 'value': {}, 'author': {tests.UID: {'role': 3, 'name': tests.UID, 'order': 0}}}, }, volume['context'][context]['releases']) diff --git a/tests/units/model/post.py b/tests/units/model/post.py index 45b85e1..655c08e 100755 --- a/tests/units/model/post.py +++ b/tests/units/model/post.py @@ -5,7 +5,6 @@ from __init__ import tests from sugar_network import db from sugar_network.client import Connection, keyfile -from sugar_network.model.user import User from sugar_network.model.context import Context from sugar_network.model.post import Post from sugar_network.toolkit.coroutine import this diff --git a/tests/units/model/routes.py b/tests/units/model/routes.py index be5ecdf..06a3dc3 100755 --- a/tests/units/model/routes.py +++ b/tests/units/model/routes.py @@ -10,7 +10,6 @@ from os.path import exists from __init__ import tests, src_root from sugar_network import db, model -from sugar_network.model.user import User from sugar_network.toolkit.router import Router, Request from sugar_network.toolkit.coroutine import this from sugar_network.toolkit import coroutine @@ -49,7 +48,7 @@ class RoutesTest(tests.Test): self.assertEqual([ {'event': 'pong'}, {'guid': 'guid', 'resource': 'document', 'event': 'create'}, - {'guid': 'guid', 'resource': 'document', 'event': 'update'}, + {'guid': 'guid', 'resource': 'document', 'event': 'update', 'props': {'prop': 'value2'}}, {'guid': 'guid', 'event': 'delete', 'resource': u'document'}, ], events) diff --git a/tests/units/node/master.py b/tests/units/node/master.py index 69fc6a7..2577ba9 100755 --- a/tests/units/node/master.py +++ b/tests/units/node/master.py @@ -20,8 +20,8 @@ from sugar_network.client import Connection, keyfile, api from sugar_network.db.directory import Directory from sugar_network import db, node, toolkit from sugar_network.node.master import MasterRoutes +from sugar_network.node.model import User from sugar_network.db.volume import Volume -from sugar_network.model.user import User from sugar_network.toolkit.router import Response, File from sugar_network.toolkit import coroutine, parcel, http @@ -64,7 +64,7 @@ class MasterTest(tests.Test): response = conn.request('POST', [], patch, params={'cmd': 'push'}) reply = parcel.decode(response.raw) - assert volume['document'].exists('1') + assert volume['document']['1'].exists blob = volume.blobs.get(hashlib.sha1('1').hexdigest()) self.assertEqual('1', ''.join(blob.iter_content())) blob = volume.blobs.get('foo/bar') @@ -165,7 +165,7 @@ class MasterTest(tests.Test): }) reply = parcel.decode(response.raw) - assert volume['document'].exists('1') + assert volume['document']['1'].exists blob_digest = hashlib.sha1('blob').hexdigest() blob = volume.blobs.get(blob_digest) self.assertEqual('blob', ''.join(blob.iter_content())) @@ -541,7 +541,7 @@ class MasterTest(tests.Test): ], [(packet.header, [dict(record) for record in packet]) for packet in parcel.decode(response.raw)]) - assert volume['document'].exists('2') + assert volume['document']['2'].exists self.assertEqual('ccc', ''.join(blob2.iter_content())) diff --git a/tests/units/node/model.py b/tests/units/node/model.py index 6788105..4187d3c 100755 --- a/tests/units/node/model.py +++ b/tests/units/node/model.py @@ -8,10 +8,10 @@ from __init__ import tests from sugar_network import db, toolkit from sugar_network.client import Connection, keyfile, api -from sugar_network.model.user import User from sugar_network.model.post import Post from sugar_network.model.context import Context from sugar_network.node import model, obs +from sugar_network.node.model import User from sugar_network.node.routes import NodeRoutes from sugar_network.toolkit.coroutine import this from sugar_network.toolkit.router import Request, Router diff --git a/tests/units/node/node.py b/tests/units/node/node.py index 0f934d4..89373dc 100755 --- a/tests/units/node/node.py +++ b/tests/units/node/node.py @@ -20,9 +20,8 @@ from sugar_network.client import Connection, keyfile, api from sugar_network.toolkit import http, coroutine from sugar_network.node.routes import NodeRoutes from sugar_network.node.master import MasterRoutes -from sugar_network.model.user import User from sugar_network.model.context import Context -from sugar_network.model.user import User +from sugar_network.node.model import User from sugar_network.toolkit.router import Router, Request, Response, fallbackroute, ACL, route from sugar_network.toolkit.coroutine import this from sugar_network.toolkit import http @@ -30,72 +29,6 @@ from sugar_network.toolkit import http class NodeTest(tests.Test): - def test_HandleDeletes(self): - volume = self.start_master() - conn = Connection(auth=http.SugarAuth(keyfile.value)) - volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY}) - - guid = this.call(method='POST', path=['context'], principal=tests.UID, content={ - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - guid_path = 'master/db/context/%s/%s' % (guid[:2], guid) - - assert exists(guid_path) - self.assertEqual({ - 'guid': guid, - 'title': 'title', - 'layer': [], - }, - this.call(method='GET', path=['context', guid], reply=['guid', 'title', 'layer'])) - self.assertEqual([], volume['context'].get(guid)['layer']) - - def subscribe(): - for event in conn.subscribe(): - events.append(event) - events = [] - coroutine.spawn(subscribe) - coroutine.dispatch() - - this.call(method='DELETE', path=['context', guid], principal=tests.UID) - coroutine.dispatch() - self.assertRaises(http.NotFound, this.call, method='GET', path=['context', guid], reply=['guid', 'title']) - self.assertEqual(['deleted'], volume['context'].get(guid)['layer']) - - def test_DeletedRestoredHandlers(self): - trigger = [] - - class TestDocument(db.Resource): - - def deleted(self): - trigger.append(False) - - def restored(self): - trigger.append(True) - - volume = self.start_master([TestDocument, User]) - conn = Connection(auth=http.SugarAuth(keyfile.value)) - - guid = conn.post(['testdocument'], {}) - self.assertEqual([], trigger) - - conn.put(['testdocument', guid, 'layer'], ['deleted']) - self.assertEqual([False], trigger) - - conn.put(['testdocument', guid, 'layer'], []) - self.assertEqual([False, True], trigger) - - conn.put(['testdocument', guid, 'layer'], ['bar']) - self.assertEqual([False, True], trigger) - - conn.put(['testdocument', guid, 'layer'], ['deleted']) - self.assertEqual([False, True, False], trigger) - - conn.put(['testdocument', guid, 'layer'], ['deleted', 'foo']) - self.assertEqual([False, True, False], trigger) - def test_RegisterUser(self): volume = self.start_master() conn = Connection(auth=http.SugarAuth(keyfile.value)) @@ -369,7 +302,7 @@ class NodeTest(tests.Test): this.call(method='GET', path=['context', guid]) self.assertNotEqual([], this.call(method='GET', path=['context'])['result']) - volume['context'].update(guid, {'layer': ['deleted']}) + volume['context'].update(guid, {'state': 'deleted'}) self.assertRaises(http.NotFound, this.call, method='GET', path=['context', guid]) self.assertEqual([], this.call(method='GET', path=['context'])['result']) @@ -415,7 +348,7 @@ class NodeTest(tests.Test): 'description': 'description', }) - self.assertRaises(http.BadRequest, this.call, method='POST', path=['context'], principal=tests.UID, content={ + self.assertRaises(RuntimeError, this.call, method='POST', path=['context'], principal=tests.UID, content={ 'guid': guid, 'type': 'activity', 'title': 'title', @@ -496,7 +429,7 @@ class NodeTest(tests.Test): self.assertEqual({ release: { - 'seqno': 6, + 'seqno': 9, 'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}}, 'value': { 'license': ['Public Domain'], @@ -568,6 +501,7 @@ class NodeTest(tests.Test): 'version': [[1], 0], 'size': len(activity_pack), 'unpack_size': len(activity_unpack), + 'content-type': 'application/vnd.olpc-sugar', }, 'dep': { 'title': 'dep', @@ -575,6 +509,7 @@ class NodeTest(tests.Test): 'version': [[2], 0], 'size': len(dep_pack), 'unpack_size': len(dep_unpack), + 'content-type': 'application/vnd.olpc-sugar', }, 'package': { 'packages': ['package.bin'], @@ -646,6 +581,7 @@ class NodeTest(tests.Test): 'version': [[1], 0], 'size': len(activity_pack), 'unpack_size': len(activity_unpack), + 'content-type': 'application/vnd.olpc-sugar', }, 'dep': { 'title': 'dep', @@ -653,6 +589,7 @@ class NodeTest(tests.Test): 'version': [[2], 0], 'size': len(dep_pack), 'unpack_size': len(dep_unpack), + 'content-type': 'application/vnd.olpc-sugar', }, 'package': { 'packages': ['package.bin'], @@ -662,7 +599,7 @@ class NodeTest(tests.Test): conn.get(['context', 'activity'], cmd='solve', stability='developer', lsb_id='Ubuntu', lsb_release='10.04', requires=['dep', 'package'])) - def test_Clone(self): + def test_Resolve(self): volume = self.start_master() conn = http.Connection(api.value, http.SugarAuth(keyfile.value)) @@ -699,7 +636,7 @@ class NodeTest(tests.Test): conn.put(['context', 'package', 'releases', '*'], {'binary': ['package.bin']}) response = Response() - reply = conn.call(Request(method='GET', path=['context', 'activity'], cmd='clone'), response) + reply = conn.call(Request(method='GET', path=['context', 'activity'], cmd='resolve'), response) assert activity_blob == reply.read() def test_AggpropInsertAccess(self): diff --git a/tests/units/node/slave.py b/tests/units/node/slave.py index 55da003..3afab04 100755 --- a/tests/units/node/slave.py +++ b/tests/units/node/slave.py @@ -14,8 +14,8 @@ from sugar_network.client import Connection, keyfile from sugar_network.node import master_api from sugar_network.node.master import MasterRoutes from sugar_network.node.slave import SlaveRoutes +from sugar_network.node.model import User from sugar_network.db.volume import Volume -from sugar_network.model.user import User from sugar_network.toolkit.router import Router, File from sugar_network.toolkit import coroutine, http, parcel diff --git a/tests/units/toolkit/router.py b/tests/units/toolkit/router.py index 0c18cee..61d8dff 100755 --- a/tests/units/toolkit/router.py +++ b/tests/units/toolkit/router.py @@ -550,6 +550,7 @@ class RouterTest(tests.Test): @postroute def _(self, request, response, result, exception): + print exception postroutes.append(('_', result, str(exception))) class B1(A): diff --git a/tests/units/toolkit/toolkit.py b/tests/units/toolkit/toolkit.py index b3e7f6b..87baedc 100755 --- a/tests/units/toolkit/toolkit.py +++ b/tests/units/toolkit/toolkit.py @@ -16,16 +16,12 @@ class ToolkitTest(tests.Test): def test_Seqno_commit(self): seqno = Seqno(tests.tmpdir + '/seqno') - self.assertEqual(False, seqno.commit()) - seqno.next() - self.assertEqual(True, seqno.commit()) - self.assertEqual(False, seqno.commit()) + seqno.commit() seqno.next() seqno = Seqno(tests.tmpdir + '/seqno') self.assertEqual(1, seqno.value) - self.assertEqual(False, seqno.commit()) def test_readline(self): |