~launchpad-pqm/launchpad/devel

8687.15.11 by Karl Fogel
Add the copyright header block to files under lib/lp/answers/.
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
3
3691.398.12 by Francis J. Lacoste
Rename test files.
4
"""Test the question workflow methods.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
5
3691.398.12 by Francis J. Lacoste
Rename test files.
6
Comprehensive tests for the question workflow methods. A narrative kind of
7
documentation is done in the ../../doc/answer-tracker-workflow.txt Doctest,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
8
but testing all the possible transitions makes the documentation more heavy
9
than necessary. This is tested here.
10
"""
11
12
__metaclass__ = type
13
14
__all__ = []
15
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
16
from datetime import (
17
    datetime,
18
    timedelta,
19
    )
20
import traceback
21
import unittest
22
23
from lazr.lifecycle.interfaces import (
24
    IObjectCreatedEvent,
25
    IObjectModifiedEvent,
26
    )
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
27
from pytz import UTC
28
from zope.component import getUtility
29
from zope.interface.verify import verifyObject
30
from zope.security.interfaces import Unauthorized
4319.2.62 by Francis J. Lacoste
Work-around to the fact it's not posisble to call linkFAQ() multiple time with the same FAQ.
31
from zope.security.proxy import removeSecurityProxy
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
32
12915.5.1 by Curtis Hovey
Rename questionenums.py to the new standard or enums.py
33
from lp.answers.enums import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
34
    QuestionAction,
35
    QuestionStatus,
36
    )
13099.1.7 by Curtis Hovey
Moved InvalidQuestionStateError to the errors module.
37
from lp.answers.errors import (
38
    InvalidQuestionStateError,
39
    NotQuestionOwnerError,
40
    )
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
41
from lp.answers.interfaces.question import IQuestion
10409.5.55 by Curtis Hovey
Removed glob imports from answers.
42
from lp.answers.interfaces.questionmessage import IQuestionMessage
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
43
from lp.registry.interfaces.distribution import IDistributionSet
44
from lp.registry.interfaces.person import (
45
    IPerson,
46
    IPersonSet,
47
    )
14606.4.7 by William Grant
canonical.lazr.testing.event -> lp.testing.event.
48
from lp.services.webapp.authorization import clear_cache
49
from lp.services.webapp.interfaces import ILaunchBag
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
50
from lp.services.worlddata.interfaces.language import ILanguageSet
14606.4.7 by William Grant
canonical.lazr.testing.event -> lp.testing.event.
51
from lp.testing import (
52
    ANONYMOUS,
53
    login,
54
    login_person,
55
    )
56
from lp.testing.event import TestEventListener
57
from lp.testing.layers import DatabaseFunctionalLayer
3691.197.17 by Francis J. Lacoste
Use layer instead of inheriting from LaunchpadFunctionalTestCase
58
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
59
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
60
class BaseAnswerTrackerWorkflowTestCase(unittest.TestCase):
61
    """Base class for test cases related to the Answer Tracker workflow.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
62
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
63
    It provides the common fixture and test helper methods.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
64
    """
65
7944.3.10 by Francis J. Lacoste
Use DatabaseFunctionalLayer
66
    layer = DatabaseFunctionalLayer
3691.197.17 by Francis J. Lacoste
Use layer instead of inheriting from LaunchpadFunctionalTestCase
67
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
68
    def setUp(self):
69
        self.now = datetime.now(UTC)
70
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
71
        # Login as the question owner.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
72
        login('no-priv@canonical.com')
73
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
74
        # Set up actors.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
75
        personset = getUtility(IPersonSet)
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
76
        # User who submits request.
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
77
        self.owner = personset.getByEmail('no-priv@canonical.com')
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
78
        # User who answers request.
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
79
        self.answerer = personset.getByEmail('test@canonical.com')
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
80
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
81
        # Admin user which can change question status.
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
82
        self.admin = personset.getByEmail('foo.bar@canonical.com')
3691.197.16 by Francis J. Lacoste
- Add setStatus() and addComment() methods.
83
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
84
        # Simple ubuntu question.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
85
        self.ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
86
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
87
        self.question = self.ubuntu.newQuestion(
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
88
            self.owner, 'Help!', 'I need help with Ubuntu',
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
89
            datecreated=self.now)
90
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
91
    def tearDown(self):
92
        if hasattr(self, 'created_event_listener'):
93
            self.created_event_listener.unregister()
94
            self.modified_event_listener.unregister()
95
7161.1.13 by Gary Poster
make lint happier, if not happy.
96
    def setQuestionStatus(self, question, new_status,
97
                          comment="Status change."):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
98
        """Utility metho to change a question status.
3691.197.104 by Francis J. Lacoste
- Protect setStatus by launchpad.Admin.
99
100
        This logs in as admin, change the status and log back as
101
        the previous user.
102
        """
103
        old_user = getUtility(ILaunchBag).user
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
104
        login_person(self.admin)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
105
        question.setStatus(self.admin, new_status, comment)
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
106
        login_person(old_user)
3691.197.104 by Francis J. Lacoste
- Protect setStatus by launchpad.Admin.
107
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
108
    def setUpEventListeners(self):
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
109
        """Install a listener for events emitted during the test."""
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
110
        self.collected_events = []
111
        if hasattr(self, 'modified_event_listener'):
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
112
            # Event listeners is already registered.
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
113
            return
114
        self.modified_event_listener = TestEventListener(
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
115
            IQuestion, IObjectModifiedEvent, self.collectEvent)
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
116
        self.created_event_listener = TestEventListener(
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
117
            IQuestionMessage, IObjectCreatedEvent, self.collectEvent)
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
118
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
119
    def collectEvent(self, object, event):
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
120
        """Collect events"""
121
        self.collected_events.append(event)
122
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
123
    def nowPlus(self, n_hours):
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
124
        """Return a DateTime a number of hours in the future."""
125
        return self.now + timedelta(hours=n_hours)
126
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
127
    def _testTransitionGuard(self, guard_name, statuses_expected_true):
128
        """Helper for transition guard tests.
129
3691.398.19 by Francis J. Lacoste
Rename most remaining classes (views, database, authorizations, notifications, scripts.)
130
        Helper that verifies that the Question guard_name attribute
10409.5.55 by Curtis Hovey
Removed glob imports from answers.
131
        is True when the question status is one listed in
132
        statuses_expected_true and False otherwise.
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
133
        """
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
134
        for status in QuestionStatus.items:
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
135
            if status != self.question.status:
136
                self.setQuestionStatus(self.question, status)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
137
            expected = status.name in statuses_expected_true
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
138
            allowed = getattr(self.question, guard_name)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
139
            self.failUnless(
140
                expected == allowed, "%s != %s when status = %s" % (
141
                    guard_name, expected, status.name))
142
143
    def _testValidTransition(self, statuses, transition_method,
144
                            expected_owner, expected_action, expected_status,
145
                            extra_message_check=None,
146
                            transition_method_args=(),
147
                            transition_method_kwargs=None,
148
                            edited_fields=None):
149
        """Helper for testing valid state transitions.
150
151
        Helper that verifies that transition_method can be called when
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
152
        the question status is one listed in statuses. It will validate the
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
153
        returned message using checkTransitionMessage(). The transition_method
154
        is called with the transition_method_args as positional parameters
155
        and transition_method_kwargs as keyword parameters.
156
157
        If extra_message_check is passed a function, it will be called with
158
        the returned message for extra checks.
159
160
        The datecreated parameter to the transition_method is set
161
        automatically to a value that will make the message sort last.
162
163
        The edited_fields parameter contain the list of field that
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
164
        are expected to be present in IObjectModifiedEvent that should
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
165
        be triggered.
166
        """
167
        self.setUpEventListeners()
7161.1.13 by Gary Poster
make lint happier, if not happy.
168
        count = 0
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
169
        if transition_method_kwargs is None:
170
            transition_method_kwargs = {}
171
        if 'datecreated' not in transition_method_kwargs:
172
            transition_method_kwargs['datecreated'] = self.nowPlus(0)
173
        for status in statuses:
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
174
            if status != self.question.status:
175
                self.setQuestionStatus(self.question, status)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
176
177
            self.collected_events = []
178
4319.2.62 by Francis J. Lacoste
Work-around to the fact it's not posisble to call linkFAQ() multiple time with the same FAQ.
179
            # Make sure that there are no FAQ linked.
180
            removeSecurityProxy(self.question).faq = None
181
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
182
            # Ensure ordering of the message.
183
            transition_method_kwargs['datecreated'] = (
184
                transition_method_kwargs['datecreated'] + timedelta(hours=1))
185
            message = transition_method(*transition_method_args,
186
                                        **transition_method_kwargs)
187
            try:
188
                self.checkTransitionMessage(
189
                    message, expected_owner=expected_owner,
190
                    expected_action=expected_action,
191
                    expected_status=expected_status)
192
                if extra_message_check:
193
                    extra_message_check(message)
194
            except AssertionError:
195
                # We capture and re-raise the error here to display a nice
196
                # message explaining in which state the transition failed.
197
                raise AssertionError(
198
                    "Failure in validating message when status=%s:\n%s" % (
199
                        status.name, traceback.format_exc(1)))
200
            self.checkTransitionEvents(
201
                message, edited_fields, status_name=status.name)
202
            count += 1
203
204
    def _testInvalidTransition(self, valid_statuses, transition_method,
205
                               *args, **kwargs):
206
        """Helper for testing invalid transitions.
207
208
        Helper that verifies that transition_method method cannot be
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
209
        called when the question status is different than the ones in
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
210
        valid_statuses.
211
212
        args and kwargs contains the parameters that should be passed to the
213
        transition method.
214
        """
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
215
        for status in QuestionStatus.items:
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
216
            if status.name in valid_statuses:
217
                continue
218
            exceptionRaised = False
219
            try:
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
220
                if status != self.question.status:
221
                    self.setQuestionStatus(self.question, status)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
222
                transition_method(*args, **kwargs)
3691.398.18 by Francis J. Lacoste
Rename interfaces.
223
            except InvalidQuestionStateError:
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
224
                exceptionRaised = True
225
            self.failUnless(exceptionRaised,
226
                            "%s() when status = %s should raise an error" % (
227
                                transition_method.__name__, status.name))
228
229
    def checkTransitionMessage(self, message, expected_owner,
230
                               expected_action, expected_status):
231
        """Helper method to check the message created by a transition.
232
3691.398.18 by Francis J. Lacoste
Rename interfaces.
233
        It make sure that the message provides IQuestionMessage and that it
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
234
        was appended to the question messages attribute. It also checks that
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
235
        the subject was computed correctly and that the new_status, action
236
        and owner attributes were set correctly.
237
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
238
        It also verifies that the question status, datelastquery (or
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
239
        datelastresponse) were updated to reflect the time of the message.
240
        """
3691.398.18 by Francis J. Lacoste
Rename interfaces.
241
        self.failUnless(verifyObject(IQuestionMessage, message))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
242
243
        self.assertEquals("Re: Help!", message.subject)
244
        self.assertEquals(expected_owner, message.owner)
245
        self.assertEquals(expected_action, message.action)
246
        self.assertEquals(expected_status, message.new_status)
247
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
248
        self.assertEquals(message, self.question.messages[-1])
249
        self.assertEquals(expected_status, self.question.status)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
250
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
251
        if expected_owner == self.question.owner:
7161.1.13 by Gary Poster
make lint happier, if not happy.
252
            self.assertEquals(message.datecreated,
253
                              self.question.datelastquery)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
254
        else:
255
            self.assertEquals(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
256
                message.datecreated, self.question.datelastresponse)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
257
258
    def checkTransitionEvents(self, message, edited_fields, status_name):
259
        """Helper method to validate the events triggered from the transition.
260
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
261
        Check that an IObjectCreatedEvent event was sent when the message
262
        was created and that an IObjectModifiedEvent was also sent.
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
263
        The event object and edited_fields attribute are checked.
264
        """
11235.5.4 by Curtis Hovey
hush lint.
265
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
266
        def failure_msg(msg):
267
            return "From status %s: %s" % (status_name, msg)
11235.5.4 by Curtis Hovey
hush lint.
268
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
269
        self.failUnless(
270
            len(self.collected_events) >= 1,
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
271
            failure_msg('failed to trigger an IObjectCreatedEvent'))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
272
        created_event = self.collected_events[0]
7876.3.12 by Francis J. Lacoste
Event.user is a principal, so we need an IPerson case.
273
        created_event_user = IPerson(created_event.user)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
274
        self.failUnless(
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
275
            IObjectCreatedEvent.providedBy(created_event),
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
276
            failure_msg(
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
277
                "%s doesn't provide IObjectCreatedEvent" % created_event))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
278
        self.failUnless(
279
            created_event.object == message,
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
280
            failure_msg("IObjectCreatedEvent contains wrong message"))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
281
        self.failUnless(
7876.3.12 by Francis J. Lacoste
Event.user is a principal, so we need an IPerson case.
282
            created_event_user == message.owner,
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
283
            failure_msg("%s != %s" % (
7876.3.12 by Francis J. Lacoste
Event.user is a principal, so we need an IPerson case.
284
                created_event_user.displayname, message.owner.displayname)))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
285
286
        self.failUnless(
287
            len(self.collected_events) == 2,
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
288
            failure_msg('failed to trigger an IObjectModifiedEvent'))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
289
        modified_event = self.collected_events[1]
7876.3.12 by Francis J. Lacoste
Event.user is a principal, so we need an IPerson case.
290
        modified_event_user = IPerson(modified_event.user)
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
291
        self.failUnless(
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
292
            IObjectModifiedEvent.providedBy(modified_event),
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
293
            failure_msg(
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
294
                "%s doesn't provide IObjectModifiedEvent"
7161.1.13 by Gary Poster
make lint happier, if not happy.
295
                % modified_event))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
296
        self.failUnless(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
297
            modified_event.object == self.question,
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
298
            failure_msg("IObjectModifiedEvent contains wrong question"))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
299
        self.failUnless(
7876.3.12 by Francis J. Lacoste
Event.user is a principal, so we need an IPerson case.
300
            modified_event_user == message.owner,
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
301
            failure_msg("%s != %s" % (
7876.3.12 by Francis J. Lacoste
Event.user is a principal, so we need an IPerson case.
302
                modified_event_user.displayname, message.owner.displayname)))
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
303
        if edited_fields:
304
            self.failUnless(
305
                set(modified_event.edited_fields) == set(edited_fields),
306
                failure_msg("%s != %s" % (
307
                    set(modified_event.edited_fields), set(edited_fields))))
308
309
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
310
class MiscAnswerTrackerWorkflowTestCase(BaseAnswerTrackerWorkflowTestCase):
311
    """Various other test cases for the Answer Tracker workflow."""
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
312
313
    def testDisallowNoOpSetStatus(self):
314
        """Test that calling setStatus to change to the same status
3691.398.18 by Francis J. Lacoste
Rename interfaces.
315
        raises an InvalidQuestionStateError.
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
316
        """
3691.197.104 by Francis J. Lacoste
- Protect setStatus by launchpad.Admin.
317
        login('foo.bar@canonical.com')
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
318
        self.assertRaises(InvalidQuestionStateError, self.question.setStatus,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
319
                self.admin, QuestionStatus.OPEN, 'Status Change')
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
320
321
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
322
class RequestInfoTestCase(BaseAnswerTrackerWorkflowTestCase):
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
323
    """Test cases for the requestInfo() workflow action method."""
324
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
325
    def test_can_request_info(self):
326
        """Test the can_request_info attribute in all the possible states."""
327
        self._testTransitionGuard(
328
            'can_request_info', ['OPEN', 'NEEDSINFO', 'ANSWERED'])
329
330
    def test_requestInfo(self):
331
        """Test that requestInfo() can be called in the OPEN, NEEDSINFO,
3691.398.18 by Francis J. Lacoste
Rename interfaces.
332
        and ANSWERED state and that it returns a valid IQuestionMessage.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
333
        """
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
334
        # Do no check the edited_fields attribute since it varies depending
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
335
        # on the departure state.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
336
        self._testValidTransition(
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
337
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO],
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
338
            expected_owner=self.answerer,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
339
            expected_action=QuestionAction.REQUESTINFO,
340
            expected_status=QuestionStatus.NEEDSINFO,
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
341
            transition_method=self.question.requestInfo,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
342
            transition_method_args=(
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
343
                self.answerer, "What's your problem?"),
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
344
            edited_fields=None)
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
345
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
346
        # Even if the question is answered, a user can request more
347
        # information, but that leave the question in the ANSWERED state.
348
        self.setQuestionStatus(self.question, QuestionStatus.ANSWERED)
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
349
        self.collected_events = []
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
350
        message = self.question.requestInfo(
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
351
            self.answerer,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
352
            "The previous answer is bad. What is the problem again?",
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
353
            datecreated=self.nowPlus(3))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
354
        self.checkTransitionMessage(
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
355
            message, expected_owner=self.answerer,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
356
            expected_action=QuestionAction.REQUESTINFO,
357
            expected_status=QuestionStatus.ANSWERED)
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
358
        self.checkTransitionEvents(
7161.1.13 by Gary Poster
make lint happier, if not happy.
359
            message, ['messages', 'datelastresponse'],
360
            QuestionStatus.OPEN.title)
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
361
362
    def test_requestInfoFromOwnerIsInvalid(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
363
        """Test that the question owner cannot use requestInfo."""
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
364
        self.assertRaises(
13099.1.3 by Curtis Hovey
Updated Question to use new error types.
365
            NotQuestionOwnerError, self.question.requestInfo,
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
366
                self.owner, 'Why should I care?', datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
367
368
    def test_requestInfoFromInvalidStates(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
369
        """Test that requestInfo cannot be called when the question status is
3691.197.52 by Francis J. Lacoste
Typos, grammar and other cleanup
370
        not OPEN, NEEDSINFO, or ANSWERED.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
371
        """
372
        self._testInvalidTransition(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
373
            ['OPEN', 'NEEDSINFO', 'ANSWERED'], self.question.requestInfo,
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
374
            self.answerer, "What's up?", datecreated=self.nowPlus(3))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
375
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
376
    def test_requestInfoPermission(self):
377
        """Test that only a logged in user can access requestInfo()."""
378
        login(ANONYMOUS)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
379
        self.assertRaises(Unauthorized, getattr, self.question, 'requestInfo')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
380
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
381
        login_person(self.answerer)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
382
        getattr(self.question, 'requestInfo')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
383
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
384
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
385
class GiveInfoTestCase(BaseAnswerTrackerWorkflowTestCase):
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
386
    """Test cases for the giveInfo() workflow action method."""
387
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
388
    def test_can_give_info(self):
389
        """Test the can_give_info attribute in all the possible states."""
390
        self._testTransitionGuard('can_give_info', ['OPEN', 'NEEDSINFO'])
391
392
    def test_giveInfoFromInvalidStates(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
393
        """Test that giveInfo cannot be called when the question status is
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
394
        not OPEN or NEEDSINFO.
395
        """
396
        self._testInvalidTransition(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
397
            ['OPEN', 'NEEDSINFO'], self.question.giveInfo,
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
398
            "That's that.", datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
399
400
    def test_giveInfo(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
401
        """Test that giveInfo() can be called when the question status is
3691.398.18 by Francis J. Lacoste
Rename interfaces.
402
        OPEN or NEEDSINFO and that it returns a valid IQuestionMessage.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
403
        """
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
404
        # Do not check the edited_fields attributes since it
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
405
        # changes based on departure state.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
406
        self._testValidTransition(
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
407
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO],
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
408
            expected_owner=self.owner,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
409
            expected_action=QuestionAction.GIVEINFO,
410
            expected_status=QuestionStatus.OPEN,
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
411
            transition_method=self.question.giveInfo,
11235.5.4 by Curtis Hovey
hush lint.
412
            transition_method_args=("That's that.", ),
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
413
            edited_fields=None)
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
414
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
415
    def test_giveInfoPermission(self):
416
        """Test that only the owner can access giveInfo()."""
417
        login(ANONYMOUS)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
418
        self.assertRaises(Unauthorized, getattr, self.question, 'giveInfo')
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
419
        login_person(self.answerer)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
420
        self.assertRaises(Unauthorized, getattr, self.question, 'giveInfo')
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
421
        login_person(self.admin)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
422
        self.assertRaises(Unauthorized, getattr, self.question, 'giveInfo')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
423
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
424
        login_person(self.owner)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
425
        getattr(self.question, 'giveInfo')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
426
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
427
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
428
class GiveAnswerTestCase(BaseAnswerTrackerWorkflowTestCase):
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
429
    """Test cases for the giveAnswer() workflow action method."""
430
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
431
    def test_can_give_answer(self):
432
        """Test the can_give_answer attribute in all the possible states."""
433
        self._testTransitionGuard(
434
            'can_give_answer', ['OPEN', 'NEEDSINFO', 'ANSWERED'])
435
436
    def test_giveAnswerFromInvalidStates(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
437
        """Test that giveAnswer cannot be called when the question status is
3691.197.52 by Francis J. Lacoste
Typos, grammar and other cleanup
438
        not OPEN, NEEDSINFO, or ANSWERED.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
439
        """
440
        self._testInvalidTransition(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
441
            ['OPEN', 'NEEDSINFO', 'ANSWERED'], self.question.giveAnswer,
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
442
            self.answerer, "The answer is this.", datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
443
4471.5.12 by Curtis Hovey
Renamed method.
444
    def test_giveAnswerByAnswerer(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
445
        """Test that giveAnswer can be called when the question status is
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
446
        one of OPEN, NEEDSINFO or ANSWERED and check that it returns a
3691.398.18 by Francis J. Lacoste
Rename interfaces.
447
        valid IQuestionMessage.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
448
        """
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
449
        # Do not check the edited_fields attributes since it
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
450
        # changes based on departure state.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
451
        self._testValidTransition(
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
452
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO,
453
             QuestionStatus.ANSWERED],
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
454
            expected_owner=self.answerer,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
455
            expected_action=QuestionAction.ANSWER,
456
            expected_status=QuestionStatus.ANSWERED,
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
457
            transition_method=self.question.giveAnswer,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
458
            transition_method_args=(
11235.5.4 by Curtis Hovey
hush lint.
459
                self.answerer, "It looks like a real problem.", ),
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
460
            edited_fields=None)
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
461
4471.5.10 by Curtis Hovey
Added a separate test for giveAnswerByOwner. Everything works, but a review is still needed.
462
    def test_giveAnswerByOwner(self):
463
        """Test giveAnswerByOwner().
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
464
4471.5.10 by Curtis Hovey
Added a separate test for giveAnswerByOwner. Everything works, but a review is still needed.
465
        Test that giveAnswer can be called by the questions owner when the
466
        question status is one of OPEN, NEEDSINFO or ANSWERED and check
467
        that it returns a valid IQuestionMessage.
468
        """
469
        # Do not check the edited_fields attributes since it
470
        # changes based on departure state.
471
        self._testValidTransition(
472
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO,
473
             QuestionStatus.ANSWERED],
474
            expected_owner=self.answerer,
475
            expected_action=QuestionAction.ANSWER,
476
            expected_status=QuestionStatus.ANSWERED,
477
            transition_method=self.question.giveAnswer,
478
            transition_method_args=(
11235.5.4 by Curtis Hovey
hush lint.
479
                self.answerer, "It looks like a real problem.", ),
4471.5.10 by Curtis Hovey
Added a separate test for giveAnswerByOwner. Everything works, but a review is still needed.
480
            edited_fields=None)
481
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
482
        # When the owner gives the answer, the question moves straight to
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
483
        # SOLVED.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
484
        def checkAnswerMessage(message):
3691.197.52 by Francis J. Lacoste
Typos, grammar and other cleanup
485
            """Check additional attributes set when the owner gives the
486
            answers.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
487
            """
4471.5.10 by Curtis Hovey
Added a separate test for giveAnswerByOwner. Everything works, but a review is still needed.
488
            self.assertEquals(None, self.question.answer)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
489
            self.assertEquals(self.owner, self.question.answerer)
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
490
            self.assertEquals(message.datecreated, self.question.date_solved)
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
491
492
        self._testValidTransition(
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
493
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO,
494
             QuestionStatus.ANSWERED],
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
495
            expected_owner=self.owner,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
496
            expected_action=QuestionAction.CONFIRM,
497
            expected_status=QuestionStatus.SOLVED,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
498
            extra_message_check=checkAnswerMessage,
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
499
            transition_method=self.question.giveAnswer,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
500
            transition_method_args=(
11235.5.4 by Curtis Hovey
hush lint.
501
                self.owner, "I found the solution.", ),
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
502
            transition_method_kwargs={'datecreated': self.nowPlus(3)},
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
503
            edited_fields=['status', 'messages', 'date_solved', 'answerer',
4471.5.8 by Curtis Hovey
Revision per review. More work is needed in test_question_workflow.py.
504
                           'datelastquery'])
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
505
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
506
    def test_giveAnswerPermission(self):
507
        """Test that only a logged in user can access giveAnswer()."""
508
        login(ANONYMOUS)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
509
        self.assertRaises(Unauthorized, getattr, self.question, 'giveAnswer')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
510
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
511
        login_person(self.answerer)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
512
        getattr(self.question, 'giveAnswer')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
513
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
514
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
515
class LinkFAQTestCase(BaseAnswerTrackerWorkflowTestCase):
516
    """Test cases for the giveAnswer() workflow action method."""
517
518
    def setUp(self):
519
        """Create an additional FAQ."""
520
        BaseAnswerTrackerWorkflowTestCase.setUp(self)
521
522
        # Only admin can create FAQ on ubuntu.
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
523
        login_person(self.admin)
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
524
        self.faq = self.ubuntu.newFAQ(
4319.3.6 by Francis J. Lacoste
Merge faqbase-db: remove url and summary field.
525
            self.admin, 'Generic HowTo', 'Describe how to do anything.')
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
526
4319.3.3 by Francis J. Lacoste
Fixes spelling mistakes and other clarifications.
527
        # Logs in as owner.
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
528
        login_person(self.owner)
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
529
530
    def test_linkFAQ(self):
531
        """Test that linkFAQ can be called when the question status is
532
        one of OPEN, NEEDSINFO or ANSWERED and check that it returns a
533
        valid IQuestionMessage.
534
        """
535
        # Do not check the edited_fields attributes since it
536
        # changes based on departure state.
537
        def checkFAQ(message):
538
            """Check that the FAQ attribute was set correctly."""
539
            self.assertEquals(self.question.faq, self.faq)
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
540
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
541
        self._testValidTransition(
542
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO,
543
             QuestionStatus.ANSWERED],
544
            expected_owner=self.answerer,
545
            expected_action=QuestionAction.ANSWER,
546
            expected_status=QuestionStatus.ANSWERED,
547
            extra_message_check=checkFAQ,
548
            transition_method=self.question.linkFAQ,
549
            transition_method_args=(
11235.5.4 by Curtis Hovey
hush lint.
550
                self.answerer, self.faq, "Check the FAQ!", ),
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
551
            edited_fields=None)
552
553
        # When the owner links the FAQ, the question moves straight to
554
        # SOLVED.
555
        def checkAnswerMessage(message):
556
            """Check additional attributes set when the owner gives the
557
            answers.
558
            """
559
            checkFAQ(message)
560
            self.assertEquals(self.owner, self.question.answerer)
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
561
            self.assertEquals(message.datecreated, self.question.date_solved)
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
562
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
563
        self._testValidTransition(
564
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO,
565
             QuestionStatus.ANSWERED],
566
            expected_owner=self.owner,
567
            expected_action=QuestionAction.CONFIRM,
568
            expected_status=QuestionStatus.SOLVED,
569
            extra_message_check=checkAnswerMessage,
570
            transition_method=self.question.linkFAQ,
571
            transition_method_args=(
11235.5.4 by Curtis Hovey
hush lint.
572
                self.owner, self.faq, "I found the solution in that FAQ.", ),
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
573
            transition_method_kwargs={'datecreated': self.nowPlus(3)},
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
574
            edited_fields=['status', 'messages', 'date_solved', 'answerer',
4319.3.28 by Francis J. Lacoste
linkFAQ by owner doesn't set answer anymore.
575
                           'datelastquery'])
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
576
577
    def test_linkFAQPermission(self):
578
        """Test that only a logged in user can access linkFAQ()."""
579
        login(ANONYMOUS)
580
        self.assertRaises(Unauthorized, getattr, self.question, 'linkFAQ')
581
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
582
        login_person(self.answerer)
4319.2.9 by Francis J. Lacoste
Add linkFAQ() method to IQuestion and other related attributes.
583
        getattr(self.question, 'linkFAQ')
584
585
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
586
class ConfirmAnswerTestCase(BaseAnswerTrackerWorkflowTestCase):
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
587
    """Test cases for the confirmAnswer() workflow action method."""
588
3691.197.67 by Francis J. Lacoste
Split two tests.
589
    def test_can_confirm_answer_without_answer(self):
590
        """Test the can_confirm_answer attribute when no answer was posted.
591
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
592
        When the question didn't receive an answer, it should always be
3691.197.67 by Francis J. Lacoste
Split two tests.
593
        false.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
594
        """
595
        self._testTransitionGuard('can_confirm_answer', [])
596
3691.197.67 by Francis J. Lacoste
Split two tests.
597
    def test_can_confirm_answer_with_answer(self):
598
        """Test that can_confirm_answer when there is an answer present.
599
600
        Once one answer was given, it becomes possible in some states.
601
        """
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
602
        self.question.giveAnswer(
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
603
            self.answerer, 'Do something about it.', self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
604
        self._testTransitionGuard(
4471.5.1 by Curtis Hovey
Revised workflow to allow the question own to set the question status to Solved without selecting an answser first. An Answer can be confirmed while in the solved state.
605
            'can_confirm_answer',
606
            ['OPEN', 'NEEDSINFO', 'ANSWERED', 'GIVEINFO', 'SOLVED'])
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
607
3691.197.67 by Francis J. Lacoste
Split two tests.
608
    def test_confirmAnswerFromInvalidStates_without_answer(self):
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
609
        """Test calling confirmAnswer from invalid states.
610
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
611
        confirmAnswer() cannot be called when the question has no message with
3691.197.67 by Francis J. Lacoste
Split two tests.
612
        action ANSWER.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
613
        """
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
614
        self._testInvalidTransition([], self.question.confirmAnswer,
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
615
            "That answer worked!.", datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
616
3691.197.67 by Francis J. Lacoste
Split two tests.
617
    def test_confirmAnswerFromInvalidStates_with_answer(self):
618
        """ Test calling confirmAnswer from invalid states with an answer.
619
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
620
        When the question has a message with action ANSWER, confirmAnswer()
3691.197.67 by Francis J. Lacoste
Split two tests.
621
        can only be called when it is in the OPEN, NEEDSINFO, or ANSWERED
622
        state.
623
        """
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
624
        answer_message = self.question.giveAnswer(
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
625
            self.answerer, 'Do something about it.', self.nowPlus(1))
4471.5.9 by Curtis Hovey
Minor dafe changes. giveAnswer is not right.
626
        self._testInvalidTransition(
627
            ['OPEN', 'NEEDSINFO', 'ANSWERED', 'SOLVED'],
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
628
            self.question.confirmAnswer, "That answer worked!.",
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
629
            answer=answer_message, datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
630
4471.5.13 by Curtis Hovey
renamed test_giveAnswer -> test_giveAnswerByOwner
631
    def test_confirmAnswerBeforeSOLVED(self):
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
632
        """Test confirmAnswer().
633
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
634
        Test that confirmAnswer() can be called when the question status
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
635
        is one of OPEN, NEEDSINFO, ANSWERED and that it has at least one
3691.398.18 by Francis J. Lacoste
Rename interfaces.
636
        ANSWER message and check that it returns a valid IQuestionMessage.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
637
        """
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
638
        answer_message = self.question.giveAnswer(
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
639
            self.answerer, "Get a grip!", datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
640
641
        def checkAnswerMessage(message):
3691.197.21 by Francis J. Lacoste
Include real location of the error. Fix problem with unordered message in confirmAnswer
642
            # Check the attributes that are set when an answer is confirmed.
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
643
            self.assertEquals(answer_message, self.question.answer)
644
            self.assertEquals(self.answerer, self.question.answerer)
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
645
            self.assertEquals(message.datecreated, self.question.date_solved)
3691.197.21 by Francis J. Lacoste
Include real location of the error. Fix problem with unordered message in confirmAnswer
646
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
647
        self._testValidTransition(
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
648
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO,
649
             QuestionStatus.ANSWERED],
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
650
            expected_owner=self.owner,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
651
            expected_action=QuestionAction.CONFIRM,
652
            expected_status=QuestionStatus.SOLVED,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
653
            extra_message_check=checkAnswerMessage,
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
654
            transition_method=self.question.confirmAnswer,
11235.5.4 by Curtis Hovey
hush lint.
655
            transition_method_args=("That was very useful.", ),
3691.197.21 by Francis J. Lacoste
Include real location of the error. Fix problem with unordered message in confirmAnswer
656
            transition_method_kwargs={'answer': answer_message,
11235.5.4 by Curtis Hovey
hush lint.
657
                                      'datecreated': self.nowPlus(2)},
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
658
            edited_fields=['status', 'messages', 'date_solved', 'answerer',
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
659
                           'answer', 'datelastquery'])
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
660
4471.5.13 by Curtis Hovey
renamed test_giveAnswer -> test_giveAnswerByOwner
661
    def test_confirmAnswerAfterSOLVED(self):
662
        """Test confirmAnswer().
663
664
        Test that confirmAnswer() can be called when the question status
665
        is SOLVED, and that it has at least one ANSWER message and check
666
        that it returns a valid IQuestionMessage.
667
        """
668
        answer_message = self.question.giveAnswer(
669
            self.answerer, "Press the any key.", datecreated=self.nowPlus(1))
670
        self.question.giveAnswer(
671
            self.owner, 'I solved my own problem.',
672
            datecreated=self.nowPlus(2))
673
        self.assertEquals(self.question.status, QuestionStatus.SOLVED)
674
675
        def checkAnswerMessage(message):
676
            # Check the attributes that are set when an answer is confirmed.
677
            self.assertEquals(answer_message, self.question.answer)
678
            self.assertEquals(self.answerer, self.question.answerer)
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
679
            self.assertEquals(message.datecreated, self.question.date_solved)
4471.5.13 by Curtis Hovey
renamed test_giveAnswer -> test_giveAnswerByOwner
680
681
        self._testValidTransition(
682
            [QuestionStatus.SOLVED],
683
            expected_owner=self.owner,
684
            expected_action=QuestionAction.CONFIRM,
685
            expected_status=QuestionStatus.SOLVED,
686
            extra_message_check=checkAnswerMessage,
687
            transition_method=self.question.confirmAnswer,
11235.5.4 by Curtis Hovey
hush lint.
688
            transition_method_args=("The space bar also works.", ),
4471.5.13 by Curtis Hovey
renamed test_giveAnswer -> test_giveAnswerByOwner
689
            transition_method_kwargs={'answer': answer_message,
11235.5.4 by Curtis Hovey
hush lint.
690
                                      'datecreated': self.nowPlus(2)},
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
691
            edited_fields=['messages', 'date_solved', 'answerer',
4471.5.13 by Curtis Hovey
renamed test_giveAnswer -> test_giveAnswerByOwner
692
                           'answer', 'datelastquery'])
693
3691.398.19 by Francis J. Lacoste
Rename most remaining classes (views, database, authorizations, notifications, scripts.)
694
    def testCannotConfirmAnAnswerFromAnotherQuestion(self):
4471.5.9 by Curtis Hovey
Minor dafe changes. giveAnswer is not right.
695
        """Test that you can't confirm an answer from a different question."""
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
696
        question1_answer = self.question.giveAnswer(
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
697
            self.answerer, 'Really, just do it!')
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
698
        question2 = self.ubuntu.newQuestion(self.owner, 'Help 2', 'Help me!')
12887.1.1 by Curtis Hovey
Ensure distribution answer contacts are also checked for dsp question targets.
699
        question2.giveAnswer(self.answerer, 'Do that!')
3691.197.16 by Francis J. Lacoste
- Add setStatus() and addComment() methods.
700
        answerRefused = False
701
        try:
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
702
            question2.confirmAnswer('That worked!', answer=question1_answer)
3691.197.16 by Francis J. Lacoste
- Add setStatus() and addComment() methods.
703
        except AssertionError:
704
            answerRefused = True
705
        self.failUnless(
706
            answerRefused, 'confirmAnswer accepted a message from a different'
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
707
            'question')
3691.197.16 by Francis J. Lacoste
- Add setStatus() and addComment() methods.
708
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
709
    def test_confirmAnswerPermission(self):
710
        """Test that only the owner can access confirmAnswer()."""
711
        login(ANONYMOUS)
7161.1.13 by Gary Poster
make lint happier, if not happy.
712
        self.assertRaises(
713
            Unauthorized, getattr, self.question, 'confirmAnswer')
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
714
        login_person(self.answerer)
7161.1.13 by Gary Poster
make lint happier, if not happy.
715
        self.assertRaises(
716
            Unauthorized, getattr, self.question, 'confirmAnswer')
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
717
        login_person(self.admin)
7161.1.13 by Gary Poster
make lint happier, if not happy.
718
        self.assertRaises(
719
            Unauthorized, getattr, self.question, 'confirmAnswer')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
720
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
721
        login_person(self.owner)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
722
        getattr(self.question, 'confirmAnswer')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
723
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
724
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
725
class ReopenTestCase(BaseAnswerTrackerWorkflowTestCase):
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
726
    """Test cases for the reopen() workflow action method."""
727
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
728
    def test_can_reopen(self):
729
        """Test the can_reopen attribute in all the possible states."""
3691.197.32 by Francis J. Lacoste
Allow reopening a SOLVED ticket.
730
        self._testTransitionGuard(
731
            'can_reopen', ['ANSWERED', 'EXPIRED', 'SOLVED'])
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
732
733
    def test_reopenFromInvalidStates(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
734
        """Test that reopen cannot be called when the question status is
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
735
        not one of OPEN, NEEDSINFO, or ANSWERED.
736
        """
737
        self._testInvalidTransition(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
738
            ['ANSWERED', 'EXPIRED', 'SOLVED'], self.question.reopen,
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
739
            "I still have a problem.", datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
740
741
    def test_reopen(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
742
        """Test that reopen() can be called when the question is in the
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
743
        ANSWERED and EXPIRED state and that it returns a valid
3691.398.18 by Francis J. Lacoste
Rename interfaces.
744
        IQuestionMessage.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
745
        """
746
        self._testValidTransition(
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
747
            [QuestionStatus.ANSWERED, QuestionStatus.EXPIRED],
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
748
            expected_owner=self.owner,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
749
            expected_action=QuestionAction.REOPEN,
750
            expected_status=QuestionStatus.OPEN,
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
751
            transition_method=self.question.reopen,
11235.5.4 by Curtis Hovey
hush lint.
752
            transition_method_args=('I still have this problem.', ),
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
753
            edited_fields=['status', 'messages', 'datelastquery'])
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
754
4471.5.11 by Curtis Hovey
Renamed reopenFromSOLVED -> reopenFromSOLVEDByOwner, added reopenFromSOLVEDByAnswerer.
755
    def test_reopenFromSOLVEDByOwner(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
756
        """Test that reopen() can be called when the question is in the
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
757
        SOLVED state (by the question owner) and that it returns an
4471.5.11 by Curtis Hovey
Renamed reopenFromSOLVED -> reopenFromSOLVEDByOwner, added reopenFromSOLVEDByAnswerer.
758
        appropriate IQuestionMessage. This transition should also clear
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
759
        the date_solved, answered and answerer attributes.
3691.197.32 by Francis J. Lacoste
Allow reopening a SOLVED ticket.
760
        """
761
        self.setUpEventListeners()
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
762
        # Mark the question as solved by the user.
763
        self.question.giveAnswer(
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
764
            self.owner, 'I solved my own problem.',
765
            datecreated=self.nowPlus(0))
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
766
        self.assertEquals(self.question.status, QuestionStatus.SOLVED)
3691.197.32 by Francis J. Lacoste
Allow reopening a SOLVED ticket.
767
3691.197.65 by Francis J. Lacoste
Turn comments into sentence.
768
        # Clear previous events.
3691.197.32 by Francis J. Lacoste
Allow reopening a SOLVED ticket.
769
        self.collected_events = []
770
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
771
        message = self.question.reopen(
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
772
            "My solution doesn't work.", datecreated=self.nowPlus(1))
3691.197.32 by Francis J. Lacoste
Allow reopening a SOLVED ticket.
773
        self.checkTransitionMessage(
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
774
            message, expected_owner=self.owner,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
775
            expected_action=QuestionAction.REOPEN,
776
            expected_status=QuestionStatus.OPEN)
3691.197.32 by Francis J. Lacoste
Allow reopening a SOLVED ticket.
777
        self.checkTransitionEvents(
4471.5.8 by Curtis Hovey
Revision per review. More work is needed in test_question_workflow.py.
778
            message, ['status', 'messages', 'answerer',
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
779
                      'date_solved', 'datelastquery'],
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
780
            QuestionStatus.OPEN.title)
3691.197.32 by Francis J. Lacoste
Allow reopening a SOLVED ticket.
781
4471.5.11 by Curtis Hovey
Renamed reopenFromSOLVED -> reopenFromSOLVEDByOwner, added reopenFromSOLVEDByAnswerer.
782
    def test_reopenFromSOLVEDByAnswerer(self):
783
        """Test that reopen() can be called when the question is in the
784
        SOLVED state (answer confirmed by the question owner) and that it
785
        returns an appropriate IQuestionMessage. This transition should
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
786
        also clear the date_solved, answered and answerer attributes.
4471.5.11 by Curtis Hovey
Renamed reopenFromSOLVED -> reopenFromSOLVEDByOwner, added reopenFromSOLVEDByAnswerer.
787
        """
788
        self.setUpEventListeners()
789
        # Mark the question as solved by the user.
790
        answer_message = self.question.giveAnswer(
791
            self.answerer, 'Press the any key.', datecreated=self.nowPlus(0))
792
        self.question.confirmAnswer("That answer worked!.",
793
            answer=answer_message, datecreated=self.nowPlus(1))
794
        self.assertEquals(self.question.status, QuestionStatus.SOLVED)
795
796
        # Clear previous events.
797
        self.collected_events = []
798
799
        message = self.question.reopen(
800
            "Where is the any key?", datecreated=self.nowPlus(1))
801
        self.checkTransitionMessage(
802
            message, expected_owner=self.owner,
803
            expected_action=QuestionAction.REOPEN,
804
            expected_status=QuestionStatus.OPEN)
805
        self.checkTransitionEvents(
806
            message, ['status', 'messages', 'answerer', 'answer',
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
807
                      'date_solved'],
4471.5.11 by Curtis Hovey
Renamed reopenFromSOLVED -> reopenFromSOLVEDByOwner, added reopenFromSOLVEDByAnswerer.
808
            QuestionStatus.OPEN.title)
809
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
810
    def test_reopenPermission(self):
811
        """Test that only the owner can access reopen()."""
812
        login(ANONYMOUS)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
813
        self.assertRaises(Unauthorized, getattr, self.question, 'reopen')
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
814
        login_person(self.answerer)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
815
        self.assertRaises(Unauthorized, getattr, self.question, 'reopen')
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
816
        login_person(self.admin)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
817
        self.assertRaises(Unauthorized, getattr, self.question, 'reopen')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
818
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
819
        login_person(self.owner)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
820
        getattr(self.question, 'reopen')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
821
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
822
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
823
class ExpireQuestionTestCase(BaseAnswerTrackerWorkflowTestCase):
3691.398.20 by Francis J. Lacoste
Rename all methods.
824
    """Test cases for the expireQuestion() workflow action method."""
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
825
3691.398.20 by Francis J. Lacoste
Rename all methods.
826
    def test_expireQuestionFromInvalidStates(self):
7161.1.13 by Gary Poster
make lint happier, if not happy.
827
        """Test that expireQuestion cannot be called when the question status
828
        is not one of OPEN or NEEDSINFO.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
829
        """
830
        self._testInvalidTransition(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
831
            ['OPEN', 'NEEDSINFO'], self.question.expireQuestion,
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
832
            self.answerer, "Too late.", datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
833
3691.398.20 by Francis J. Lacoste
Rename all methods.
834
    def test_expireQuestion(self):
7161.1.13 by Gary Poster
make lint happier, if not happy.
835
        """Test that expireQuestion() can be called when the question status
836
        is OPEN or NEEDSINFO and that it returns a valid IQuestionMessage.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
837
        """
838
        self._testValidTransition(
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
839
            [QuestionStatus.OPEN, QuestionStatus.NEEDSINFO],
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
840
            expected_owner=self.answerer,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
841
            expected_action=QuestionAction.EXPIRE,
842
            expected_status=QuestionStatus.EXPIRED,
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
843
            transition_method=self.question.expireQuestion,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
844
            transition_method_args=(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
845
                self.answerer, 'This question is expired.'),
3691.197.31 by Francis J. Lacoste
All workflow methods now triggers an ISQLObjectModifiedEvent.
846
            edited_fields=['status', 'messages', 'datelastresponse'])
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
847
3691.398.20 by Francis J. Lacoste
Rename all methods.
848
    def test_expireQuestionPermission(self):
849
        """Test that only a logged in user can access expireQuestion()."""
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
850
        login(ANONYMOUS)
7161.1.13 by Gary Poster
make lint happier, if not happy.
851
        self.assertRaises(
852
            Unauthorized, getattr, self.question, 'expireQuestion')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
853
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
854
        login_person(self.answerer)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
855
        getattr(self.question, 'expireQuestion')
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
856
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
857
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
858
class RejectTestCase(BaseAnswerTrackerWorkflowTestCase):
3691.197.97 by Francis J. Lacoste
Splitted tests by workflow methods.
859
    """Test cases for the reject() workflow action method."""
860
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
861
    def test_rejectFromInvalidStates(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
862
        """Test that reject() cannot be called when the question status is
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
863
        not one of OPEN or NEEDSINFO.
864
        """
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
865
        valid_statuses = [status.name for status in QuestionStatus.items
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
866
                          if status.name != 'INVALID']
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
867
        # Reject user must be an answer contact, (or admin, or product owner).
4215.2.15 by Curtis Hovey
Added English to Lp. Rvised tests; removing old rules and added new rule. Translation tests probably fail. This branch needs some cleaning too.
868
        # Answer contacts must speak a language
869
        self.answerer.addLanguage(getUtility(ILanguageSet)['en'])
12959.4.23 by Curtis Hovey
Always pass the subscribed_by argument to addAnswerContact.
870
        self.ubuntu.addAnswerContact(self.answerer, self.answerer)
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
871
        login_person(self.answerer)
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
872
        self._testInvalidTransition(
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
873
            valid_statuses, self.question.reject,
3691.197.66 by Francis J. Lacoste
PEP-8 cleanup.
874
            self.answerer, "This is lame.", datecreated=self.nowPlus(1))
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
875
876
    def test_reject(self):
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
877
        """Test that reject() can be called when the question status is
3691.398.18 by Francis J. Lacoste
Rename interfaces.
878
        OPEN or NEEDSINFO and that it returns a valid IQuestionMessage.
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
879
        """
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
880
        # Reject user must be an answer contact, (or admin, or product owner).
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
881
        login_person(self.answerer)
4215.2.15 by Curtis Hovey
Added English to Lp. Rvised tests; removing old rules and added new rule. Translation tests probably fail. This branch needs some cleaning too.
882
        # Answer contacts must speak a language
883
        self.answerer.addLanguage(getUtility(ILanguageSet)['en'])
12959.4.23 by Curtis Hovey
Always pass the subscribed_by argument to addAnswerContact.
884
        self.ubuntu.addAnswerContact(self.answerer, self.answerer)
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
885
        valid_statuses = [status for status in QuestionStatus.items
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
886
                          if status.name != 'INVALID']
3691.197.33 by Francis J. Lacoste
Consider a rejection message as answering the ticket.
887
888
        def checkRejectMessageIsAnAnswer(message):
889
            # Check that the rejection message was considered answering
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
890
            # the question.
891
            self.assertEquals(message, self.question.answer)
892
            self.assertEquals(self.answerer, self.question.answerer)
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
893
            self.assertEquals(message.datecreated, self.question.date_solved)
3691.197.33 by Francis J. Lacoste
Consider a rejection message as answering the ticket.
894
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
895
        self._testValidTransition(
896
            valid_statuses,
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
897
            expected_owner=self.answerer,
3691.398.17 by Francis J. Lacoste
Rename dbschemas.
898
            expected_action=QuestionAction.REJECT,
899
            expected_status=QuestionStatus.INVALID,
3691.197.33 by Francis J. Lacoste
Consider a rejection message as answering the ticket.
900
            extra_message_check=checkRejectMessageIsAnAnswer,
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
901
            transition_method=self.question.reject,
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
902
            transition_method_args=(
3691.197.64 by Francis J. Lacoste
Rename actor variable according to role instead of name
903
                self.answerer, 'This is lame.'),
4664.2.4 by Curtis Hovey
Renamed attribute from datesolved to date_solved. Doh. This was
904
            edited_fields=['status', 'messages', 'answerer', 'date_solved',
3691.197.33 by Francis J. Lacoste
Consider a rejection message as answering the ticket.
905
                           'answer', 'datelastresponse'])
3691.197.15 by Francis J. Lacoste
* Made all workflow transitions checks the ticket state to ensure only
906
3691.197.104 by Francis J. Lacoste
- Protect setStatus by launchpad.Admin.
907
    def testRejectPermission(self):
908
        """Test the reject() access control.
909
3691.398.22 by Francis J. Lacoste
Rename support facet to answers.
910
        Only an answer contacts and administrator can reject a question.
3691.197.104 by Francis J. Lacoste
- Protect setStatus by launchpad.Admin.
911
        """
912
        login(ANONYMOUS)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
913
        self.assertRaises(Unauthorized, getattr, self.question, 'reject')
3691.197.104 by Francis J. Lacoste
- Protect setStatus by launchpad.Admin.
914
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
915
        login_person(self.owner)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
916
        self.assertRaises(Unauthorized, getattr, self.question, 'reject')
3691.197.104 by Francis J. Lacoste
- Protect setStatus by launchpad.Admin.
917
6596.1.6 by Guilherme Salgado
Replace a bunch of login() calls with login_person() to workaround the new restriction on EmailAddress.email
918
        login_person(self.answerer)
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
919
        self.assertRaises(Unauthorized, getattr, self.question, 'reject')
3691.197.104 by Francis J. Lacoste
- Protect setStatus by launchpad.Admin.
920
4215.2.15 by Curtis Hovey
Added English to Lp. Rvised tests; removing old rules and added new rule. Translation tests probably fail. This branch needs some cleaning too.
921
        # Answer contacts must speak a language
922
        self.answerer.addLanguage(getUtility(ILanguageSet)['en'])
12959.4.23 by Curtis Hovey
Always pass the subscribed_by argument to addAnswerContact.
923
        self.question.target.addAnswerContact(self.answerer, self.answerer)
12887.1.1 by Curtis Hovey
Ensure distribution answer contacts are also checked for dsp question targets.
924
        # clear authorization cache for check_permission
925
        clear_cache()
926
        self.assertTrue(
927
            getattr(self.question, 'reject'),
928
            "Answer contact cannot reject question.")
929
        login_person(self.admin)
930
        self.assertTrue(
931
            getattr(self.question, 'reject'),
932
            "Admin cannot reject question.")
933
934
    def testRejectPermission_indirect_answer_contact(self):
935
        # Indirect answer contacts (for a distribution) can reject
936
        # distribuiton source package questions.
937
        login_person(self.admin)
938
        dsp = self.ubuntu.getSourcePackage('mozilla-firefox')
939
        self.question.target = dsp
940
        login_person(self.answerer)
941
        self.answerer.addLanguage(getUtility(ILanguageSet)['en'])
12959.4.23 by Curtis Hovey
Always pass the subscribed_by argument to addAnswerContact.
942
        self.ubuntu.addAnswerContact(self.answerer, self.answerer)
12887.1.1 by Curtis Hovey
Ensure distribution answer contacts are also checked for dsp question targets.
943
        self.assertTrue(
944
            getattr(self.question, 'reject'),
945
            "Answer contact cannot reject question.")