tests.py 11.7 KB
Newer Older
Victor Shnayder committed
1 2 3 4 5 6 7 8 9 10 11
import json
import logging
from functools import partial

from django.contrib.auth.models import User
from django.test import TestCase
from django.test.client import RequestFactory
from django.conf import settings
from django.core.urlresolvers import reverse

from foldit.views import foldit_ops, verify_code
12
from foldit.models import PuzzleComplete, Score
Victor Shnayder committed
13 14 15
from student.models import UserProfile, unique_id_for_user

from datetime import datetime, timedelta
16
from pytz import UTC
Victor Shnayder committed
17 18 19 20 21 22 23 24 25 26 27 28

log = logging.getLogger(__name__)


class FolditTestCase(TestCase):

    def setUp(self):
        self.factory = RequestFactory()
        self.url = reverse('foldit_ops')

        pwd = 'abc'
        self.user = User.objects.create_user('testuser', 'test@test.com', pwd)
29
        self.user2 = User.objects.create_user('testuser2', 'test2@test.com', pwd)
Victor Shnayder committed
30
        self.unique_user_id = unique_id_for_user(self.user)
31
        self.unique_user_id2 = unique_id_for_user(self.user2)
32
        now = datetime.now(UTC)
Victor Shnayder committed
33 34 35 36
        self.tomorrow = now + timedelta(days=1)
        self.yesterday = now - timedelta(days=1)

        UserProfile.objects.create(user=self.user)
37
        UserProfile.objects.create(user=self.user2)
Victor Shnayder committed
38

Julian Arni committed
39
    def make_request(self, post_data, user=None):
Victor Shnayder committed
40
        request = self.factory.post(self.url, post_data)
Julian Arni committed
41
        request.user = self.user if not user else user
Victor Shnayder committed
42 43
        return request

Julian Arni committed
44
    def make_puzzle_score_request(self, puzzle_ids, best_scores, user=None):
45 46 47 48
        """
        Given lists of puzzle_ids and best_scores (must have same length), make a
        SetPlayerPuzzleScores request and return the response.
        """
Julian Arni committed
49 50 51 52
        if not(type(best_scores) == list):
            best_scores = [best_scores]
        if not(type(puzzle_ids) == list):
            puzzle_ids = [puzzle_ids]
Julian Arni committed
53 54
        user = self.user if not user else user

55 56
        def score_dict(puzzle_id, best_score):
            return {"PuzzleID": puzzle_id,
Victor Shnayder committed
57
                    "ScoreType": "score",
58 59 60 61 62
                    "BestScore": best_score,
                    # current scores don't actually matter
                    "CurrentScore": best_score + 0.01,
                    "ScoreVersion": 23}
        scores = [score_dict(pid, bs) for pid, bs in zip(puzzle_ids, best_scores)]
Victor Shnayder committed
63 64
        scores_str = json.dumps(scores)

65 66
        verify = {"Verify": verify_code(user.email, scores_str),
                  "VerifyMethod": "FoldItVerify"}
Victor Shnayder committed
67 68 69
        data = {'SetPlayerPuzzleScoresVerify': json.dumps(verify),
                'SetPlayerPuzzleScores': scores_str}

Julian Arni committed
70
        request = self.make_request(data, user)
Victor Shnayder committed
71 72 73

        response = foldit_ops(request)
        self.assertEqual(response.status_code, 200)
74 75 76 77
        return response

    def test_SetPlayerPuzzleScores(self):

Julian Arni committed
78
        puzzle_id = 994391
79
        best_score = 0.078034
Julian Arni committed
80
        response = self.make_puzzle_score_request(puzzle_id, [best_score])
Victor Shnayder committed
81 82 83 84

        self.assertEqual(response.content, json.dumps(
            [{"OperationID": "SetPlayerPuzzleScores",
              "Value": [{
85
                  "PuzzleID": puzzle_id,
Victor Shnayder committed
86 87
                  "Status": "Success"}]}]))

88
        # There should now be a score in the db.
Julian Arni committed
89
        top_10 = Score.get_tops_n(10, puzzle_id)
90 91
        self.assertEqual(len(top_10), 1)
        self.assertEqual(top_10[0]['score'], Score.display_score(best_score))
Victor Shnayder committed
92

93
    def test_SetPlayerPuzzleScores_many(self):
Victor Shnayder committed
94

95
        response = self.make_puzzle_score_request([1, 2], [0.078034, 0.080000])
Victor Shnayder committed
96 97 98 99

        self.assertEqual(response.content, json.dumps(
            [{"OperationID": "SetPlayerPuzzleScores",
              "Value": [{
100
                  "PuzzleID": 1,
Victor Shnayder committed
101 102
                  "Status": "Success"},

103
                  {"PuzzleID": 2,
Victor Shnayder committed
104 105 106
                  "Status": "Success"}]}]))


107 108 109 110 111 112
    def test_SetPlayerPuzzleScores_multiple(self):
        """
        Check that multiple posts with the same id are handled properly
        (keep latest for each user, have multiple users work properly)
        """
        orig_score = 0.07
Julian Arni committed
113
        puzzle_id = '1'
114 115 116
        response = self.make_puzzle_score_request([puzzle_id], [orig_score])

        # There should now be a score in the db.
Julian Arni committed
117
        top_10 = Score.get_tops_n(10, puzzle_id)
118
        self.assertEqual(len(top_10), 1)
Julian Arni committed
119
        self.assertEqual(top_10[0]['score'], Score.display_score(orig_score))
120 121 122 123 124

        # Reporting a better score should overwrite
        better_score = 0.06
        response = self.make_puzzle_score_request([1], [better_score])

Julian Arni committed
125
        top_10 = Score.get_tops_n(10, puzzle_id)
126
        self.assertEqual(len(top_10), 1)
Julian Arni committed
127 128 129 130 131

        # Floats always get in the way, so do almostequal
        self.assertAlmostEqual(top_10[0]['score'],
               Score.display_score(better_score),
               delta=0.5)
132 133 134 135 136

        # reporting a worse score shouldn't
        worse_score = 0.065
        response = self.make_puzzle_score_request([1], [worse_score])

Julian Arni committed
137
        top_10 = Score.get_tops_n(10, puzzle_id)
138 139
        self.assertEqual(len(top_10), 1)
        # should still be the better score
Julian Arni committed
140 141 142
        self.assertAlmostEqual(top_10[0]['score'],
                Score.display_score(better_score),
                delta=0.5)
143

Julian Arni committed
144
    def test_SetPlayerPuzzleScores_manyplayers(self):
145 146
        """
        Check that when we send scores from multiple users, the correct order
Julian Arni committed
147 148
        of scores is displayed. Note that, before being processed by
        display_score, lower scores are better.
149 150
        """
        puzzle_id = ['1']
Julian Arni committed
151 152
        player1_score = 0.08
        player2_score = 0.02
Julian Arni committed
153
        response1 = self.make_puzzle_score_request(puzzle_id, player1_score,
154 155 156
                self.user)

        # There should now be a score in the db.
Julian Arni committed
157
        top_10 = Score.get_tops_n(10, puzzle_id)
158 159 160
        self.assertEqual(len(top_10), 1)
        self.assertEqual(top_10[0]['score'], Score.display_score(player1_score))

Julian Arni committed
161
        response2 = self.make_puzzle_score_request(puzzle_id, player2_score,
162 163 164
                self.user2)

        # There should now be two scores in the db
Julian Arni committed
165
        top_10 = Score.get_tops_n(10, puzzle_id)
166 167 168
        self.assertEqual(len(top_10), 2)

        # Top score should be player2_score. Second should be player1_score
Julian Arni committed
169 170 171 172 173 174
        self.assertAlmostEqual(top_10[0]['score'],
                Score.display_score(player2_score),
                delta=0.5)
        self.assertAlmostEqual(top_10[1]['score'],
                Score.display_score(player1_score),
                delta=0.5)
175

176 177
        # Top score user should be self.user2.username
        self.assertEqual(top_10[0]['username'], self.user2.username)
178

Victor Shnayder committed
179 180
    def test_SetPlayerPuzzleScores_error(self):

181
        scores = [{"PuzzleID": 994391,
Victor Shnayder committed
182 183
                    "ScoreType": "score",
                    "BestScore": 0.078034,
184 185
                    "CurrentScore": 0.080035,
                    "ScoreVersion": 23}]
Victor Shnayder committed
186 187 188
        validation_str = json.dumps(scores)

        verify = {"Verify": verify_code(self.user.email, validation_str),
189
                  "VerifyMethod": "FoldItVerify"}
Victor Shnayder committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

        # change the real string -- should get an error
        scores[0]['ScoreVersion'] = 22
        scores_str = json.dumps(scores)

        data = {'SetPlayerPuzzleScoresVerify': json.dumps(verify),
                'SetPlayerPuzzleScores': scores_str}

        request = self.make_request(data)

        response = foldit_ops(request)
        self.assertEqual(response.status_code, 200)

        response_data = json.loads(response.content)

        self.assertEqual(response.content,
                         json.dumps([{
                             "OperationID": "SetPlayerPuzzleScores",
                             "Success": "false",
                             "ErrorString": "Verification failed",
                             "ErrorCode": "VerifyFailed"}]))


    def make_puzzles_complete_request(self, puzzles):
        """
        Make a puzzles complete request, given an array of
        puzzles.  E.g.

        [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
          {"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]
        """
        puzzles_str = json.dumps(puzzles)

        verify = {"Verify": verify_code(self.user.email, puzzles_str),
                  "VerifyMethod":"FoldItVerify"}

226
        data = {'SetPuzzlesCompleteVerify': json.dumps(verify),
Victor Shnayder committed
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
                'SetPuzzlesComplete': puzzles_str}

        request = self.make_request(data)

        response = foldit_ops(request)
        self.assertEqual(response.status_code, 200)
        return response

    @staticmethod
    def set_puzzle_complete_response(values):
        return json.dumps([{"OperationID":"SetPuzzlesComplete",
                            "Value": values}])


    def test_SetPlayerPuzzlesComplete(self):

        puzzles = [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
                    {"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(response.content,
                         self.set_puzzle_complete_response([13, 53524]))



    def test_SetPlayerPuzzlesComplete_multiple(self):
        """Check that state is stored properly"""

        puzzles = [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
                    {"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(response.content,
                         self.set_puzzle_complete_response([13, 53524]))

        puzzles = [ {"PuzzleID": 14, "Set": 1, "SubSet": 3},
                    {"PuzzleID": 15, "Set": 1, "SubSet": 1} ]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(response.content,
                         self.set_puzzle_complete_response([13, 14, 15, 53524]))



    def test_SetPlayerPuzzlesComplete_level_complete(self):
        """Check that the level complete function works"""

        puzzles = [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
                    {"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(response.content,
                         self.set_puzzle_complete_response([13, 53524]))

        puzzles = [ {"PuzzleID": 14, "Set": 1, "SubSet": 3},
                    {"PuzzleID": 15, "Set": 1, "SubSet": 1} ]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertEqual(response.content,
                         self.set_puzzle_complete_response([13, 14, 15, 53524]))

        is_complete = partial(
            PuzzleComplete.is_level_complete, self.unique_user_id)

        self.assertTrue(is_complete(1, 1))
        self.assertTrue(is_complete(1, 3))
        self.assertTrue(is_complete(1, 2))
        self.assertFalse(is_complete(4, 5))

        puzzles = [ {"PuzzleID": 74, "Set": 4, "SubSet": 5} ]

        response = self.make_puzzles_complete_request(puzzles)

        self.assertTrue(is_complete(4, 5))

        # Now check due dates

        self.assertTrue(is_complete(1, 1, due=self.tomorrow))
        self.assertFalse(is_complete(1, 1, due=self.yesterday))



    def test_SetPlayerPuzzlesComplete_error(self):

        puzzles = [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
                    {"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]

        puzzles_str = json.dumps(puzzles)

        verify = {"Verify": verify_code(self.user.email, puzzles_str + "x"),
                  "VerifyMethod":"FoldItVerify"}

        data = {'SetPuzzlesCompleteVerify': json.dumps(verify),
                'SetPuzzlesComplete': puzzles_str}

        request = self.make_request(data)

        response = foldit_ops(request)
        self.assertEqual(response.status_code, 200)

        response_data = json.loads(response.content)

        self.assertEqual(response.content,
                         json.dumps([{
                             "OperationID": "SetPuzzlesComplete",
                             "Success": "false",
                             "ErrorString": "Verification failed",
                             "ErrorCode": "VerifyFailed"}]))