Passed
Pull Request — devel (#121)
by Paolo
17:57
created

AsyncBioSamplesTestCase.check_samples()   A

Complexity

Conditions 3

Size

Total Lines 20
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 20
rs 9.45
c 0
b 0
f 0
cc 3
nop 1
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
"""
4
Created on Tue Jan 21 10:54:09 2020
5
6
@author: Paolo Cozzi <[email protected]>
7
"""
8
9
import os
10
import json
11
import time
12
import types
13
import asynctest
14
15
from aioresponses import aioresponses
16
from aiohttp.client_exceptions import ServerDisconnectedError
17
from unittest.mock import patch, Mock
18
19
from django.test import TestCase
20
from django.utils import timezone
21
22
from common.constants import SUBMITTED, READY, COMPLETED
23
from uid.models import Animal as UIDAnimal, Sample as UIDSample
24
25
from ..tasks.cleanup import (
26
    check_samples, get_orphan_samples, PAGE_SIZE, BIOSAMPLE_ACCESSION_ENDPOINT,
27
    BIOSAMPLE_SAMPLE_ENDPOINT)
28
from ..models import OrphanSample, ManagedTeam
29
30
from .common import generate_token, BioSamplesMixin
31
32
# get my path
33
dir_path = os.path.dirname(os.path.realpath(__file__))
34
35
# define data path
36
DATA_PATH = os.path.join(dir_path, "data")
37
38
39
with open(os.path.join(DATA_PATH, "page_0.json")) as handle:
40
    page0 = handle.read()
41
42
with open(os.path.join(DATA_PATH, "page_1.json")) as handle:
43
    page1 = handle.read()
44
45
with open(os.path.join(DATA_PATH, "issue_page1.json")) as handle:
46
    issue_page1 = handle.read()
47
48
mocked_accessions = {}
49
50
for accession in ["SAMEA6376679", "SAMEA6376682", "SAMEA6376980",
51
                  "SAMEA6376982", "SAMEA6376991", "SAMEA6376992"]:
52
    with open(os.path.join(DATA_PATH, f"{accession}.json")) as handle:
53
        mocked_accessions[accession] = handle.read()
54
55
56
class AsyncBioSamplesTestCase(asynctest.TestCase, TestCase):
57
58
    fixtures = [
59
        'biosample/managedteam',
60
        'uid/animal',
61
        'uid/dictbreed',
62
        'uid/dictcountry',
63
        'uid/dictrole',
64
        'uid/dictsex',
65
        'uid/dictspecie',
66
        'uid/dictstage',
67
        'uid/dictuberon',
68
        'uid/ontology',
69
        'uid/organization',
70
        'uid/publication',
71
        'uid/sample',
72
        'uid/submission',
73
        'uid/user'
74
    ]
75
76
    @classmethod
77
    def setUpClass(cls):
78
        # calling my base class setup
79
        super().setUpClass()
80
81
        cls.mock_auth_patcher = patch('pyUSIrest.auth.requests.get')
82
        cls.mock_auth = cls.mock_auth_patcher.start()
83
84
    @classmethod
85
    def tearDownClass(cls):
86
        cls.mock_auth_patcher.stop()
87
88
        # calling base method
89
        super().tearDownClass()
90
91
    def setUp(self):
92
        # calling my base setup
93
        super().setUp()
94
95
        # well, updating data and set two biosample ids. Those are not
96
        # orphans
97
        animal = UIDAnimal.objects.get(pk=1)
98
        animal.biosample_id = "SAMEA6376980"
99
        animal.save()
100
101
        sample = UIDSample.objects.get(pk=1)
102
        sample.biosample_id = "SAMEA6376982"
103
        sample.save()
104
105
        # generate tocken
106
        self.mock_auth.return_value = Mock()
107
        self.mock_auth.return_value.text = generate_token()
108
        self.mock_auth.return_value.status_code = 200
109
110
    async def check_samples(self):
111
        with aioresponses() as mocked:
112
            mocked.get(
113
                '{url}?filter=attr:project:IMAGE&size={size}'.format(
114
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
115
                status=200,
116
                body=page0)
117
            mocked.get(
118
                '{url}?filter=attr:project:IMAGE&page=1&size={size}'.format(
119
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
120
                status=200,
121
                body=page1)
122
            for accession, body in mocked_accessions.items():
123
                mocked.get(
124
                    '{url}/{accession}'.format(
125
                        url=BIOSAMPLE_SAMPLE_ENDPOINT, accession=accession),
126
                    status=200,
127
                    body=body)
128
129
            await check_samples()
130
131
    async def test_request(self) -> None:
132
        await self.check_samples()
133
134
        # get accessions
135
        reference = ['SAMEA6376991', 'SAMEA6376992']
136
137
        self.assertEqual(OrphanSample.objects.count(), 2)
138
139
        # check objects into UID
140
        for accession in reference:
141
            orphan = OrphanSample.objects.get(biosample_id=accession)
142
            orphan.status = READY
143
144
    async def test_request_with_issues(self) -> None:
145
        """Test a temporary issue with BioSamples reply"""
146
147
        with aioresponses() as mocked:
148
            mocked.get(
149
                '{url}?filter=attr:project:IMAGE&size={size}'.format(
150
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
151
                status=200,
152
                body=page0)
153
            mocked.get(
154
                '{url}?filter=attr:project:IMAGE&page=1&size={size}'.format(
155
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
156
                status=200,
157
                body=issue_page1)
158
            for accession, body in mocked_accessions.items():
159
                mocked.get(
160
                    '{url}/{accession}'.format(
161
                        url=BIOSAMPLE_SAMPLE_ENDPOINT, accession=accession),
162
                    status=200,
163
                    body=body)
164
165
            await check_samples()
166
167
        # no objects where tracked since issue in response
168
        self.assertEqual(OrphanSample.objects.count(), 0)
169
170
    async def test_request_with_html(self) -> None:
171
        """Test a not JSON response (HTML)"""
172
173
        with aioresponses() as mocked:
174
            mocked.get(
175
                '{url}?filter=attr:project:IMAGE&size={size}'.format(
176
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
177
                status=200,
178
                body=page0)
179
            mocked.get(
180
                '{url}?filter=attr:project:IMAGE&page=1&size={size}'.format(
181
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
182
                status=200,
183
                headers={'Content-type': 'text/html'},
184
                body="<html>Not a JSON</html>")
185
            for accession, body in mocked_accessions.items():
186
                mocked.get(
187
                    '{url}/{accession}'.format(
188
                        url=BIOSAMPLE_SAMPLE_ENDPOINT, accession=accession),
189
                    status=200,
190
                    body=body)
191
192
            await check_samples()
193
194
        # no objects where tracked since issue in response
195
        self.assertEqual(OrphanSample.objects.count(), 0)
196
197
    async def test_biosamples_down(self) -> None:
198
        """Test a not JSON response (HTML): BioSamples down"""
199
200
        with aioresponses() as mocked:
201
            mocked.get(
202
                '{url}?filter=attr:project:IMAGE&size={size}'.format(
203
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
204
                status=200,
205
                headers={'Content-type': 'text/html'},
206
                body="<html>Not a JSON</html>")
207
            mocked.get(
208
                '{url}?filter=attr:project:IMAGE&page=1&size={size}'.format(
209
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
210
                status=200,
211
                headers={'Content-type': 'text/html'},
212
                body="<html>Not a JSON</html>")
213
214
            with self.assertRaises(ConnectionError):
215
                await check_samples()
216
217
        # no objects where tracked since issue in response
218
        self.assertEqual(OrphanSample.objects.count(), 0)
219
220
    async def test_server_lost(self) -> None:
221
        """Test server disconnect error"""
222
223
        with aioresponses() as mocked:
224
            mocked.get(
225
                '{url}?filter=attr:project:IMAGE&size={size}'.format(
226
                    url=BIOSAMPLE_ACCESSION_ENDPOINT, size=PAGE_SIZE),
227
                exception=ServerDisconnectedError()
228
            )
229
230
            with self.assertRaises(ConnectionError):
231
                await check_samples()
232
233
        # no objects where tracked since issue in response
234
        self.assertEqual(OrphanSample.objects.count(), 0)
235
236
    async def test_already_removed_samples(self) -> None:
237
        """Test check samples with entries in database"""
238
239
        # create items into database. get team first
240
        team = ManagedTeam.objects.get(pk=1)
241
242
        sample1 = OrphanSample.objects.create(
243
            biosample_id='SAMEA6376991',
244
            name="IMAGEA000005610",
245
            team=team,
246
            status=SUBMITTED,
247
        )
248
249
        sample2 = OrphanSample.objects.create(
250
            biosample_id='SAMEA6376992',
251
            name="IMAGEA000005607",
252
            team=team,
253
            status=COMPLETED,
254
            removed=True,
255
            removed_at=timezone.now()
256
        )
257
258
        await self.check_samples()
259
260
        # test: there are the same samples in database
261
        self.assertEqual(OrphanSample.objects.count(), 2)
262
263
        # no changes in statuses
264
        self.assertEqual(sample1.status, SUBMITTED)
265
        self.assertEqual(sample2.status, COMPLETED)
266
267
268
class PurgeOrphanSampleTestCase(BioSamplesMixin, TestCase):
269
    fixtures = [
270
        'biosample/managedteam',
271
        'biosample/orphansample',
272
        'uid/dictspecie',
273
    ]
274
275
    def test_purge_orphan_samples(self):
276
        """Test biosample data conversion"""
277
278
        with open(os.path.join(DATA_PATH, "SAMEA6376982.json")) as handle:
279
            data = json.load(handle)
280
281
        self.mock_get.return_value = Mock()
282
        self.mock_get.return_value.json.return_value = data
283
        self.mock_get.return_value.status_code = 200
284
285
        # call my method
286
        samples = get_orphan_samples()
287
288
        # teams is now a generator
289
        self.assertIsInstance(samples, types.GeneratorType)
290
        samples = list(samples)
291
292
        self.assertEqual(len(samples), 2)
293
294
        sample = samples[0]
295
        self.assertIsInstance(sample, dict)
296
297
        sample = samples[1]
298
        self.assertIsInstance(sample, dict)
299
300
        # read the team from data
301
        team = sample['team']
302
        self.assertIsInstance(team, ManagedTeam)
303
304
    def test_purge_orphan_samples_not_ready(self):
305
        """Test not ready orphan samples"""
306
307
        # Simulate a different status
308
        OrphanSample.objects.update(status=SUBMITTED)
309
        orphan_count = sum(1 for orphan in get_orphan_samples())
310
311
        self.assertEqual(orphan_count, 0)
312
313
    def test_purge_orphan_samples_ignore(self):
314
        """Test ignored orphan samples"""
315
316
        # Ignoring samples gives no object
317
        OrphanSample.objects.update(ignore=True)
318
        orphan_count = sum(1 for orphan in get_orphan_samples())
319
320
        self.assertEqual(orphan_count, 0)
321
322
    def test_purge_orphan_samples_removed(self):
323
        """Test removed orphan samples"""
324
325
        # Ignoring samples gives no object
326
        OrphanSample.objects.update(removed=True)
327
        orphan_count = sum(1 for orphan in get_orphan_samples())
328
329
        self.assertEqual(orphan_count, 0)
330
331
    def test_purge_orphan_samples_with_limit(self):
332
        """Test get orphan samples with limits"""
333
334
        orphan_count = sum(1 for orphan in get_orphan_samples(limit=1))
335
        self.assertEqual(orphan_count, 1)
336
337
    def test_purge_orphan_private(self):
338
        """Test no access to a BioSamples id (already removed?)"""
339
340
        data = {
341
            'timestamp': int(time.time() * 1000),
342
            'status': 403,
343
            'error': 'Forbidden',
344
            'exception': (
345
                'uk.ac.ebi.biosamples.service.'
346
                'BioSamplesAapService$SampleNotAccessibleException'),
347
            'message': (
348
                'This sample is private and not available for browsing. '
349
                'If you think this is an error and/or you should have access '
350
                'please contact the BioSamples Helpdesk at biosamples@'
351
                'ebi.ac.uk'),
352
            'path': '/biosamples/samples/SAMEA6376982'
353
        }
354
355
        # override mock object
356
        self.mock_get.return_value = Mock()
357
        self.mock_get.return_value.json.return_value = data
358
        self.mock_get.return_value.status_code = 403
359
360
        orphan_count = sum(1 for orphan in get_orphan_samples(limit=1))
361
        self.assertEqual(orphan_count, 0)
362