~launchpad-pqm/launchpad/devel

11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
11204.7.6 by Jeroen Vermeulen
Bit more docstring.
4
"""Fake, in-process implementation of the Librarian API.
5
6
This works in-process only.  It does not support exchange of files
7
between processes, or URL access.  Nor will it completely support all
11204.7.7 by Jeroen Vermeulen
Typo.
8
details of the Librarian interface.  But where it's enough, this
9
provides a simple and fast alternative to the full Librarian in unit
10
tests.
11204.7.6 by Jeroen Vermeulen
Bit more docstring.
11
"""
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
12
13
__metaclass__ = type
14
__all__ = [
15
    'FakeLibrarian',
16
    ]
17
18
import hashlib
19
from StringIO import StringIO
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
20
from urlparse import urljoin
21
11562.2.2 by Robert Collins
Simplify the fake librarian.
22
from fixtures import Fixture
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
23
import transaction
24
from transaction.interfaces import ISynchronizer
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
25
import zope.component
26
from zope.interface import implements
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
27
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
28
from canonical.config import config
29
from canonical.librarian.client import get_libraryfilealias_download_path
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
30
from canonical.librarian.interfaces import (
31
    ILibrarianClient,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
32
    LIBRARIAN_SERVER_DEFAULT_TIMEOUT,
33
    )
14578.2.1 by William Grant
Move librarian stuff from canonical.launchpad to lp.services.librarian. canonical.librarian remains untouched.
34
from lp.services.librarian.interfaces import ILibraryFileAliasSet
35
from lp.services.librarian.model import (
36
    LibraryFileAlias,
37
    LibraryFileContent,
38
    )
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
39
40
41
class InstrumentedLibraryFileAlias(LibraryFileAlias):
42
    """A `ILibraryFileAlias` implementation that fakes library access."""
43
44
    file_committed = False
45
46
    def checkCommitted(self):
47
        """Raise an error if this file has not been committed yet."""
48
        if not self.file_committed:
11204.7.3 by Jeroen Vermeulen
Run same tests against both real and fake librarian.
49
            raise LookupError(
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
50
                "Attempting to retrieve file '%s' from the fake "
51
                "librarian, but the file has not yet been committed to "
52
                "storage." % self.filename)
53
54
    def open(self, timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT):
55
        self.checkCommitted()
56
        self._datafile = StringIO(self.content_string)
57
58
    def read(self, chunksize=None, timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT):
59
        return self._datafile.read(chunksize)
60
61
11562.2.2 by Robert Collins
Simplify the fake librarian.
62
class FakeLibrarian(Fixture):
63
    """A test double Librarian which works in-process.
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
64
65
    This takes the role of both the librarian client and the LibraryFileAlias
66
    utility.
67
    """
11204.7.3 by Jeroen Vermeulen
Run same tests against both real and fake librarian.
68
    provided_utilities = [ILibrarianClient, ILibraryFileAliasSet]
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
69
    implements(ISynchronizer, *provided_utilities)
70
11562.2.2 by Robert Collins
Simplify the fake librarian.
71
    def setUp(self):
72
        """Fixture API: install as the librarian."""
73
        Fixture.setUp(self)
74
        self.aliases = {}
75
        self.download_url = config.librarian.download_url
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
76
        transaction.manager.registerSynch(self)
11562.2.2 by Robert Collins
Simplify the fake librarian.
77
        self.addCleanup(transaction.manager.unregisterSynch, self)
11204.7.4 by Jeroen Vermeulen
Restore original utilities properly, thanks Gary\!
78
79
        site_manager = zope.component.getGlobalSiteManager()
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
80
        for utility in self.provided_utilities:
11204.7.4 by Jeroen Vermeulen
Restore original utilities properly, thanks Gary\!
81
            original = zope.component.getUtility(utility)
82
            if site_manager.unregisterUtility(original, utility):
11562.2.2 by Robert Collins
Simplify the fake librarian.
83
                # We really disabled a utility, restore it later.
84
                self.addCleanup(
85
                    zope.component.provideUtility, original, utility)
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
86
            zope.component.provideUtility(self, utility)
11562.2.2 by Robert Collins
Simplify the fake librarian.
87
            self.addCleanup(site_manager.unregisterUtility, self, utility)
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
88
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
89
    def addFile(self, name, size, file, contentType, expires=None):
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
90
        """See `IFileUploadClient`."""
11496.1.1 by Jeroen Vermeulen
FakeLibrarian.create should return LibraryFileAlias.
91
        return self._storeFile(
92
            name, size, file, contentType, expires=expires).id
93
94
    def _storeFile(self, name, size, file, contentType, expires=None):
95
        """Like `addFile`, but returns the `LibraryFileAlias`."""
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
96
        content = file.read()
97
        real_size = len(content)
98
        if real_size != size:
11204.7.3 by Jeroen Vermeulen
Run same tests against both real and fake librarian.
99
            raise AssertionError(
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
100
                "Uploading '%s' to the fake librarian with incorrect "
101
                "size %d; actual size is %d." % (name, size, real_size))
102
103
        file_ref = self._makeLibraryFileContent(content)
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
104
        alias = self._makeAlias(file_ref.id, name, content, contentType)
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
105
        self.aliases[alias.id] = alias
11496.1.1 by Jeroen Vermeulen
FakeLibrarian.create should return LibraryFileAlias.
106
        return alias
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
107
108
    def remoteAddFile(self, name, size, file, contentType, expires=None):
109
        """See `IFileUploadClient`."""
110
        return NotImplementedError()
111
7675.809.18 by Robert Collins
Extend the private librarian concept to have unique domains to get unique security contexts on browsers.
112
    def getURLForAlias(self, aliasID, secure=False):
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
113
        """See `IFileDownloadClient`."""
11515.1.1 by Jeroen Vermeulen
Make Librarian URLs use LibraryFileAlias objects, not ids.
114
        return self.getURLForAliasObject(self.aliases.get(int(aliasID)))
115
116
    def getURLForAliasObject(self, alias):
117
        """See `IFileDownloadClient`."""
118
        if alias.deleted:
119
            return None
120
        path = get_libraryfilealias_download_path(alias.id, alias.filename)
11204.7.2 by Jeroen Vermeulen
Teach the FakeLibrarian to register/unregister itself as utilities.
121
        return urljoin(self.download_url, path)
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
122
123
    def getFileByAlias(self, aliasID,
124
                       timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT):
125
        """See `IFileDownloadClient`."""
126
        alias = self[aliasID]
127
        alias.checkCommitted()
128
        return StringIO(alias.content_string)
129
11393.2.1 by Jeroen Vermeulen
Let FakeLibrarian-using tests avoid transaction.commit().
130
    def pretendCommit(self):
131
        """Pretend that there's been a commit.
132
133
        When you add a file to the librarian (real or fake), it is not
134
        fully available until the transaction that added the file has
135
        been committed.  Call this method to make the FakeLibrarian act
136
        as if there's been a commit, without actually committing a
137
        database transaction.
138
        """
139
        # Note that all files have been committed to storage.
140
        for alias in self.aliases.itervalues():
141
            alias.file_committed = True
142
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
143
    def _makeAlias(self, file_id, name, content, content_type):
144
        """Create a `LibraryFileAlias`."""
145
        alias = InstrumentedLibraryFileAlias(
146
            contentID=file_id, filename=name, mimetype=content_type)
147
        alias.content_string = content
148
        return alias
149
150
    def _makeLibraryFileContent(self, content):
151
        """Create a `LibraryFileContent`."""
152
        size = len(content)
153
        sha1 = hashlib.sha1(content).hexdigest()
154
        md5 = hashlib.md5(content).hexdigest()
155
156
        content_object = LibraryFileContent(filesize=size, sha1=sha1, md5=md5)
157
        return content_object
158
159
    def create(self, name, size, file, contentType, expires=None,
160
               debugID=None, restricted=False):
161
        "See `ILibraryFileAliasSet`."""
11496.1.1 by Jeroen Vermeulen
FakeLibrarian.create should return LibraryFileAlias.
162
        return self._storeFile(name, size, file, contentType, expires=expires)
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
163
164
    def __getitem__(self, key):
165
        "See `ILibraryFileAliasSet`."""
166
        alias = self.aliases.get(key)
167
        if alias is None:
11204.7.3 by Jeroen Vermeulen
Run same tests against both real and fake librarian.
168
            raise LookupError(
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
169
                "Attempting to retrieve file alias %d from the fake "
170
                "librarian, who has never heard of it." % key)
171
        return alias
172
173
    def findBySHA1(self, sha1):
174
        "See `ILibraryFileAliasSet`."""
175
        for alias in self.aliases.itervalues():
176
            if alias.content.sha1 == sha1:
177
                return alias
178
179
        return None
180
181
    def beforeCompletion(self, txn):
182
        """See `ISynchronizer`."""
183
184
    def afterCompletion(self, txn):
185
        """See `ISynchronizer`."""
11393.2.1 by Jeroen Vermeulen
Let FakeLibrarian-using tests avoid transaction.commit().
186
        self.pretendCommit()
11204.7.1 by Jeroen Vermeulen
A fake Librarian for use in tests.
187
188
    def newTransaction(self, txn):
189
        """See `ISynchronizer`."""