tests.py 25.9 KB
Newer Older
1
2
3
"""
iPPI-DB unit tests
"""
4
import json
5
6
import re

7
from django.contrib.auth import get_user_model
8
from django.core.management import call_command
Hervé  MENAGER's avatar
Hervé MENAGER committed
9
from django.test import TestCase
10
from django.urls import reverse
11
import requests
Hervé  MENAGER's avatar
Hervé MENAGER committed
12

13
14
15
16
17
18
19
20
21
22
23
24
25
26
from ippidb import models
from ippidb.ws import (
    get_uniprot_info,
    get_doi_info,
    get_pfam_info,
    get_google_patent_info,
    PatentNotFound,
    get_pubmed_info,
    get_pdb_uniprot_mapping,
    get_pdb_pfam_mapping,
    EntryNotFoundError,
    convert_iupac_to_smiles_and_inchi,
    convert_smiles_to_iupac,
)
Hervé  MENAGER's avatar
Hervé MENAGER committed
27
from ippidb.models import (
Hervé  MENAGER's avatar
Hervé MENAGER committed
28
29
30
    Compound,
    CompoundTanimoto,
    create_tanimoto,
31
    update_compound_cached_properties,
32
33
34
    CompoundAction,
    Ppi,
    Contribution,
35
36
    LeLleBiplotData,
    PcaBiplotData,
Hervé  MENAGER's avatar
Hervé MENAGER committed
37
38
    DrugBankCompound,
    Protein,
Hervé  MENAGER's avatar
Hervé MENAGER committed
39
)
40
from .utils import create_dummy_compound, create_dummy_drugbank_compound
41

42

43
class MolSmiTestCase(TestCase):
44
    """
45
    Test MOL to SMILES and SMILES to MOL format conversion iPPI-DB web services
46
    """
47
48

    def setUp(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
49
50
51
        self.smiles = "C"
        # the MOL version is also a valid regexp to validate arbitrary name in
        # the openbabel-generated version
Hervé  MENAGER's avatar
Hervé MENAGER committed
52
53
54
        self.mol = (
            "\n OpenBabel[0-9]{11}D\n\n  1  0  0  0  0  0  0  0 "
            " 0  0999 V2000\n    1.0000    0.0000    0.0000 C   0  0  0  0 "
Hervé  MENAGER's avatar
Hervé MENAGER committed
55
            " 0  0  0  0  0  0  0  0\nM  END\n"
Hervé  MENAGER's avatar
Hervé MENAGER committed
56
        )
57

58
59
60
61
    def test_view_smi2mol_valid(self):
        url = reverse("smi2mol")
        response = self.client.get(url, {"smiString": self.smiles})
        self.assertEqual(response.status_code, 200)
62
        self.assertTrue(re.compile(self.mol).match(response.json()["mol"]))
63
64
65

    def test_view_smi2mol_empty(self):
        url = reverse("smi2mol")
66
        response = self.client.get(url, {"smiString": ""})
67
68
69
70
        self.assertEqual(response.status_code, 400)

    def test_view_smi2mol_invalid(self):
        url = reverse("smi2mol")
71
        response = self.client.get(url, {"smiString": "###"})
72
73
74
75
        self.assertEqual(response.status_code, 400)

    def test_view_smi2mol_withspaces(self):
        url = reverse("smi2mol")
76
        response = self.client.get(url, {"smiString": "\n  " + self.smiles + "  \n  "})
77
        self.assertEqual(response.status_code, 200)
78
        self.assertTrue(re.compile(self.mol).match(response.json()["mol"]))
79
80
81
82
83

    def test_view_mol2smi_valid(self):
        url = reverse("mol2smi")
        response = self.client.get(url, {"molString": self.mol})
        self.assertEqual(response.status_code, 200)
84
        self.assertEqual(response.json(), {"smiles": self.smiles})
85
86
87

    def test_view_mol2smi_empty(self):
        url = reverse("mol2smi")
88
        response = self.client.get(url, {"molString": ""})
89
90
91
92
        self.assertEqual(response.status_code, 400)

    def test_view_mol2smi_invalid(self):
        url = reverse("mol2smi")
93
        response = self.client.get(url, {"molString": "###"})
94
95
        self.assertEqual(response.status_code, 400)

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
96
97


98
99
class CompoundTanimotoTestCase(TestCase):
    def setUp(self):
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
100
        self.smiles_dict = {1: "CC", 2: "CCC"}
Hervé  MENAGER's avatar
Hervé MENAGER committed
101
        self.query = "CC"
102
        for id_, smiles in self.smiles_dict.items():
103
            create_dummy_compound(id_, smiles)
104
105

    def test_create(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
106
107
        create_tanimoto(self.query, "FP4")
        ct = CompoundTanimoto.objects.get(id=1, canonical_smiles=self.query)
108
        self.assertEqual(ct.tanimoto, 1.0)
Hervé  MENAGER's avatar
Hervé MENAGER committed
109
        ct = CompoundTanimoto.objects.get(id=2, canonical_smiles=self.query)
110
        self.assertEqual(ct.tanimoto, 0.5)
Hervé  MENAGER's avatar
Hervé MENAGER committed
111
112


Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
113
class CompoundTanimotoTestCaseCompound1ECFP4(TestCase):
Hervé  MENAGER's avatar
Hervé MENAGER committed
114
    def setUp(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
115
116
117
        self.smiles_dict = {
            1: "CC(C)C(=O)c1cc(C(=O)c2ccc(Oc3ccccc3)cc2)c(O)c(O)c1O",
            2: "NC(=N)N[C@H](C1CCCCC1)C(=O)NCC(=O)N1CCC(CC1)c1cc(n[nH]1)"
Hervé  MENAGER's avatar
Hervé MENAGER committed
118
119
            "-c1ccc(Cl)cc1Cl",
        }
Hervé  MENAGER's avatar
Hervé MENAGER committed
120
        self.query = "CC(C)C(=O)c1cc(C(=O)c2ccc(Oc3ccccc3)cc2)c(O)c(O)c1O"
Hervé  MENAGER's avatar
Hervé MENAGER committed
121
        for id_, smiles in self.smiles_dict.items():
122
            create_dummy_compound(id_, smiles)
Hervé  MENAGER's avatar
Hervé MENAGER committed
123
124

    def test_create(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
125
126
127
        create_tanimoto(self.query, "ECFP4")
        ct = CompoundTanimoto.objects.get(id=1, canonical_smiles=self.query)
        ct2 = CompoundTanimoto.objects.get(id=2, canonical_smiles=self.query)
Hervé  MENAGER's avatar
Hervé MENAGER committed
128
        self.assertEqual(ct.tanimoto, 1.0)
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
129
130
        self.assertEqual(float(ct2.tanimoto), 0.0971)

131
132
133
134

class CompoundAnnotationsTestCase(TestCase):
    def test_lipinsky_ok(self):
        # c is ok for Lipinsky
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
135
        c = create_dummy_compound(1, "CC")
136
137
138
139
140
141
142
143
144
        # MW <= 500
        c.molecular_weight = 300
        # HBA <= 10
        c.nb_acceptor_h = 9
        # HBD <= 5
        c.nb_donor_h = 4
        # AlogP <= 5
        c.a_log_p = 4
        c.save()
145
        update_compound_cached_properties()
146
147
148
149
150
151
152
        c = Compound.objects.get(id=1)
        self.assertTrue(c.lipinsky_mw)
        self.assertTrue(c.lipinsky_hba)
        self.assertTrue(c.lipinsky_hbd)
        self.assertTrue(c.lipinsky_a_log_p)
        self.assertTrue(c.lipinsky)

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
153
    def test_lipinsky_ko(self):
154
        # c is ko for Lipinsky
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
155
        c = create_dummy_compound(1, "CC")
156
157
158
159
160
161
162
163
164
        # MW > 500
        c.molecular_weight = 3000
        # HBA > 10
        c.nb_acceptor_h = 11
        # HBD > 5
        c.nb_donor_h = 6
        # AlogP > 5
        c.a_log_p = 7
        c.save()
165
        update_compound_cached_properties()
166
167
168
169
170
171
172
173
174
        c = Compound.objects.get(id=1)
        self.assertFalse(c.lipinsky_mw)
        self.assertFalse(c.lipinsky_hba)
        self.assertFalse(c.lipinsky_hbd)
        self.assertFalse(c.lipinsky_a_log_p)
        self.assertFalse(c.lipinsky)

    def test_veber_ok(self):
        # c is ok for Veber
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
175
        c = create_dummy_compound(1, "CC")
176
177
178
179
180
181
182
183
        # HBA + HBD <=12
        c.nb_acceptor_h = 10
        c.nb_donor_h = 1
        # TPSA <= 140
        c.tpsa = 130
        # RB <= 10
        c.nb_rotatable_bonds = 9
        c.save()
184
        update_compound_cached_properties()
185
186
187
188
189
190
191
192
        c = Compound.objects.get(id=1)
        self.assertTrue(c.veber_hba_hbd)
        self.assertTrue(c.veber_tpsa)
        self.assertTrue(c.veber_rb)
        self.assertTrue(c.veber)

    def test_veber_ko(self):
        # c is ko for Veber
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
193
        c = create_dummy_compound(1, "CC")
194
195
196
197
198
199
200
201
        # HBA + HBD > 12
        c.nb_acceptor_h = 10
        c.nb_donor_h = 3
        # TPSA > 140
        c.tpsa = 141
        # RB > 10
        c.nb_rotatable_bonds = 11
        c.save()
202
        update_compound_cached_properties()
203
204
205
206
207
208
209
210
        c = Compound.objects.get(id=1)
        self.assertFalse(c.veber_hba_hbd)
        self.assertFalse(c.veber_tpsa)
        self.assertFalse(c.veber_rb)
        self.assertFalse(c.veber)

    def test_pfizer_ok(self):
        # c is ok for Pfizer
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
211
        c = create_dummy_compound(1, "CC")
212
213
214
215
216
        # AlogP <=3
        c.a_log_p = 2
        # TPSA >=75
        c.tpsa = 80
        c.save()
217
        update_compound_cached_properties()
218
219
220
221
222
223
224
        c = Compound.objects.get(id=1)
        self.assertTrue(c.pfizer_a_log_p)
        self.assertTrue(c.pfizer_tpsa)
        self.assertTrue(c.pfizer)

    def test_pfizer_ko(self):
        # c is ko for Pfizer
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
225
        c = create_dummy_compound(1, "CC")
226
227
228
229
230
        # AlogP >3
        c.a_log_p = 4
        # TPSA <75
        c.tpsa = 8
        c.save()
231
        update_compound_cached_properties()
232
233
234
        c = Compound.objects.get(id=1)
        self.assertFalse(c.pfizer_a_log_p)
        self.assertFalse(c.pfizer_tpsa)
235
236
        self.assertFalse(c.pfizer)

Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
237

238
class QueryCompoundViewsTestCase(TestCase):
239
240
    @classmethod
    def setUpTestData(cls):
241
242
        create_dummy_compound(1, "CC")
        create_dummy_compound(2, "CCC")
Hervé  MENAGER's avatar
Hervé MENAGER committed
243
244
        call_command("lle_le")
        call_command("pca")
245
246
247
248
249
250

    def test_existing_compound(self):
        """
        The detail view of an existing compound
        returns a 200
        """
Hervé  MENAGER's avatar
Hervé MENAGER committed
251
        url = reverse("compound_card", kwargs={"pk": 1})
252
253
254
255
256
257
258
259
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_inexisting_compound(self):
        """
        The detail view of a non-existing compound
        returns a 404 not found.
        """
Hervé  MENAGER's avatar
Hervé MENAGER committed
260
        url = reverse("compound_card", kwargs={"pk": 9})
261
262
263
264
265
266
267
268
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_compounds_list(self):
        """
        The compounds list view
        returns a 200
        """
Hervé  MENAGER's avatar
Hervé MENAGER committed
269
        url = reverse("compound_list")
270
271
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
272

Hervé  MENAGER's avatar
Hervé MENAGER committed
273

274
275
276
def create_dummy_user(login, password, admin=False):
    User = get_user_model()
    if admin:
277
278
279
        user = User.objects.create_superuser(
            username=login, email=f"{login}@ippidb.test", password=password
        )
280
    else:
281
282
283
        user = User.objects.create_user(
            username=login, email=f"{login}@ippidb.test", password=password
        )
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
    return user


def create_dummy_contribution(compound_id, smiles, user, symmetry, validate=False):
    c = create_dummy_compound(compound_id, smiles)
    ppi = Ppi()
    ppi.symmetry = symmetry
    ppi.save()
    ca = CompoundAction()
    ca.nb_copy_compounds = 1
    ca.compound = c
    ca.ppi = ppi
    ca.save()
    co = Contribution()
    co.ppi = ppi
    co.contributor = user
    co.validated = validate
    co.save()
    return c


305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
def set_dummy_lelle_pca_props(c):
    c.le = 0.5
    c.lle = 0.5
    c.molecular_weight = 1
    c.a_log_p = 1
    c.nb_donor_h = 1
    c.nb_acceptor_h = 1
    c.tpsa = 1
    c.nb_rotatable_bonds = 1
    c.nb_benzene_like_rings = 1
    c.fsp3 = 1
    c.nb_chiral_centers = 1
    c.nb_csp3 = 1
    c.nb_atom = 1
    c.nb_bonds = 1
    c.nb_atom_non_h = 1
    c.nb_rings = 1
    c.nb_multiple_bonds = 1
    c.nb_aromatic_bonds = 1
    c.aromatic_ratio = 1
    c.best_activity_ppi_family_name = "dummy"
    c.save()


329
330
331
332
333
334
335
336
337
338
339
340
341
342
class QueryCompoundViewsAccessTestCase(TestCase):
    """
    Test the visibility of compounds belonging to
    validated or unvalidated contributions
    The "visibility matrix" is the following:
    ============================================================================
    |Validation status / User| Anonymous | Creator | Another user | Admin user |
    | Unvalidated            |    No     |   Yes   |     No       |    Yes     |
    | Validated              |    Yes    |   Yes   |     Yes      |    Yes     |
    ============================================================================
    """

    @classmethod
    def setUpTestData(cls):
343
344
345
346
347
348
349
350
351
        """
        Test data include:
        - one "contributor" user
        - one "other" user
        - one "admin" user
        - one unvalidated contribution
        - one validated contribution
        - 20 other compounds, so that a PCA can actually be computed on the data
        """
352
353
354
355
356
357
358
359
360
361
362
363
364
        symmetry = models.Symmetry()
        symmetry.code = "AS"
        symmetry.description = "asymmetric"
        symmetry.save()
        # create contributor 1
        cls.user_c1 = create_dummy_user("contributor1", "test1")
        # create contributor 2
        cls.user_c2 = create_dummy_user("contributor2", "test2")
        # create admin
        cls.user_cA = create_dummy_user("admin", "testA", True)
        # WHAT ARE THE USE CASES TO BE TESTED?
        # user is anonymous, contributor, another contributor, admin
        # compound is not validated, validated
365
366
367
368
369
370
371
        cls.c1 = create_dummy_contribution(1, "CC", cls.user_c1, symmetry, False)
        cls.c2 = create_dummy_contribution(2, "CCC", cls.user_c1, symmetry, True)
        # create another 20 compounds to enable PCA
        for i in range(1, 21):
            create_dummy_compound(2 + i, "O" + i * "C")
        for c in Compound.objects.all():
            set_dummy_lelle_pca_props(c)
372
373
374
        call_command("lle_le")
        call_command("pca")

375
376
377
378
    def test_is_validated(self):
        """
        Test the is_validated property is valid for each compound
        """
379
380
        self.assertEqual(self.c1.is_validated(), False)
        self.assertEqual(self.c2.is_validated(), True)
381
        for i in range(1, 21):
382
383
384
            # these compounds are considered not validated because
            # are not linked to a CompoundAction
            self.assertEqual(Compound.objects.get(id=2 + i).is_validated(), False)
385

386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
    def test_lelleplot_filter(self):
        """
        Test that generated LE-LLE biplot
        does not include unvalidated compounds (21 for this test dataset)
        """
        self.assertEqual(
            len(json.loads(LeLleBiplotData.objects.get().le_lle_biplot_data)), 21
        )

    def test_pcaplot_filter(self):
        """
        Test that generated PCA biplot
        does not include unvalidated compounds (21 for this test dataset)
        """
        call_command("pca")
        self.assertEqual(
            len(json.loads(PcaBiplotData.objects.get().pca_biplot_data).get("data")), 21
        )

405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
    def test_compound_detail_unvalidated_unlogged(self):
        """
        Unvalidated compound should not be visible
        if unlogged
        """
        url = reverse("compound_card", kwargs={"pk": 1})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)

    def test_compound_detail_unvalidated_logged_creator(self):
        """
        Unvalidated compound should be visible
        if logged as the creator
        """
        url = reverse("compound_card", kwargs={"pk": 1})
        self.client.force_login(self.user_c1)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.client.logout()

    def test_compound_detail_unvalidated_logged_user(self):
        """
        Unvalidated compound should not be visible
        if logged as another user
        """
        url = reverse("compound_card", kwargs={"pk": 1})
        self.client.force_login(self.user_c2)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 404)
        self.client.logout()

    def test_compound_detail_unvalidated_logged_admin(self):
        """
        Unvalidated compound should be visible
        if logged as an admin user
        """
        url = reverse("compound_card", kwargs={"pk": 1})
        self.client.force_login(self.user_cA)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.client.logout()

    def test_compound_detail_validated_unlogged(self):
        """
        Validated compound should be visible
        if unlogged
        """
        url = reverse("compound_card", kwargs={"pk": 2})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_compound_detail_validated_logged_creator(self):
        """
        Validated compound should be visible
        if logged as the creator
        """
        url = reverse("compound_card", kwargs={"pk": 2})
        self.client.force_login(self.user_c1)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.client.logout()

    def test_compound_detail_validated_logged_user(self):
        """
        Validated compound should be visible
        if logged as another user
        """
        url = reverse("compound_card", kwargs={"pk": 2})
        self.client.force_login(self.user_c2)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.client.logout()

    def test_compound_detail_validated_logged_admin(self):
        """
        Validated compound should be visible
        if logged as an admin user
        """
        url = reverse("compound_card", kwargs={"pk": 2})
        self.client.force_login(self.user_cA)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.client.logout()


490
491
492
493
494
495
class TestGetDoiInfo(TestCase):
    """
    Test retrieving information for a DOI entry
    """

    def test_get_doi_info(self):
496
        try:
497
            resp = get_doi_info("10.1073/pnas.0805139105")
498
499
500
        except requests.exceptions.HTTPError as he:
            # skip this test if the DOI server throws an error
            # (that happens)
501
            print(f"server-side DOI resolution error, skipping...\nError is {he}")
502
            return
Hervé  MENAGER's avatar
Hervé MENAGER committed
503
504
505
506
507
508
509
510
511
512
513
514
        self.assertEqual(
            resp["title"], "A quantitative atlas of mitotic" " phosphorylation"
        )
        self.assertEqual(
            resp["journal_name"], "Proceedings of the National" " Academy of Sciences"
        )
        self.assertEqual(resp["biblio_year"], 2008)
        self.assertEqual(
            resp["authors_list"],
            "Dephoure N., Zhou C., Villen J., Beausoleil S. A., "
            "Bakalarski C. E., Elledge S. J., Gygi S. P.",
        )
515
516


517
class TestGetPfamInfo(TestCase):
518
    def test_get_pfam_info(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
519
        target = {"id": "bZIP_1", "description": "bZIP transcription factor"}
520
        resp = get_pfam_info("PF00170")
521
522
523
        self.assertEqual(resp, target)


524
525
class TestGetGooglePatentInfo(TestCase):
    def test_it(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
526
527
528
529
530
531
532
533
534
        target = {
            "title": "Secure virtual machine bootstrap in untrusted"
            " cloud infrastructures",
            "journal_name": None,
            "biblio_year": "2010",
            "authors_list": "Fabio R. Maino, Pere Monclus, David A."
            " McGrew, Robert T. Bell, "
            "Steven Joseph Rich, Cisco Technology Inc",
        }
535
        resp = get_google_patent_info("US8856504")
536
537
538
        self.assertEqual(resp, target)

    def test_entry_not_found(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
539
        self.assertRaises(
540
541
            PatentNotFound,
            get_google_patent_info,
Hervé  MENAGER's avatar
Hervé MENAGER committed
542
543
            "US8856504US8856504US885US8856504US8856504",
        )
544
545


546
547
548
549
550
class TestProtein(TestCase):
    def test_create_protein_no_gene_name(self):
        # this test ensures that we can save a protein with no gene name
        # in the DB
        p = Protein()
551
        p.uniprot_id = "P00784"
552
553
554
        try:
            p.save()
        except Exception as exc:
555
            self.fail(f"exception {exc} raised while saving protein P00784")
556
557


558
class TestGetPubMEDIdInfo(TestCase):
559
    def test_get_pubmed_info(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
560
561
562
563
564
565
566
        target = {
            "title": "Gene List significance at-a-glance with" " GeneValorization.",
            "journal_name": "Bioinformatics (Oxford, England)",
            "biblio_year": "2011",
            "authors_list": "Brancotte B, Biton A, Bernard-Pierrot I, "
            "Radvanyi F, Reyal F, Cohen-Boulakia S",
        }
567
        resp = get_pubmed_info("21349868")
568
569
570
        self.assertEqual(resp, target)


571
572
573
574
575
576
class TestGetUniprotInfo(TestCase):
    """
    Test retrieving information for a uniprot entry
    """

    def test_get_uniprot_info(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
        resp = get_uniprot_info("Q15286")
        self.assertEqual(resp["recommended_name"], "Ras-related protein Rab-35")
        self.assertEqual(resp["organism"], 9606)
        self.assertEqual(resp["gene_id"], 11021)
        exp_gene_names = [
            {"name": "RAB35", "type": "primary"},
            {"name": "RAB1C", "type": "synonym"},
            {"name": "RAY", "type": "synonym"},
        ]
        self.assertEqual(
            sorted(resp["gene_names"], key=lambda k: k["name"]),
            sorted(exp_gene_names, key=lambda k: k["name"]),
        )
        self.assertEqual(resp["entry_name"], "RAB35_HUMAN")
        self.assertEqual(resp["short_name"], "RAB35")
        exp_molecular_functions = [
            "GO_0003924",
            "GO_0005525",
            "GO_0005546",
            "GO_0019003",
        ]
        self.assertEqual(
            sorted(resp["molecular_functions"]), sorted(exp_molecular_functions)
        )
        exp_cellular_localisations = [
            "GO_0005829",
            "GO_0005886",
            "GO_0005905",
            "GO_0010008",
606
            "GO_0012505",
Hervé  MENAGER's avatar
Hervé MENAGER committed
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
            "GO_0030665",
            "GO_0031253",
            "GO_0042470",
            "GO_0045171",
            "GO_0045334",
            "GO_0055038",
            "GO_0070062",
            "GO_0098993",
        ]
        self.assertEqual(
            sorted(resp["cellular_localisations"]), sorted(exp_cellular_localisations)
        )
        exp_biological_processes = [
            "GO_0000281",
            "GO_0006886",
            "GO_0008104",
            "GO_0016197",
            "GO_0019882",
            "GO_0031175",
            "GO_0032456",
            "GO_0032482",
            "GO_0036010",
            "GO_0048227",
            "GO_1990090",
        ]
        self.assertEqual(
            sorted(resp["biological_processes"]), sorted(exp_biological_processes)
        )
        exp_accessions = ["Q15286", "B2R6E0", "B4E390"]
        self.assertEqual(sorted(resp["accessions"]), sorted(exp_accessions))
637
        exp_citations = [
Hervé  MENAGER's avatar
Hervé MENAGER committed
638
639
640
641
642
643
644
645
646
647
648
649
650
651
            {"doi": "10.1006/bbrc.1994.2889", "pmid": "7811277"},
            {"doi": "10.1038/ng1285", "pmid": "14702039"},
            {"doi": "10.1038/nature04569", "pmid": "16541075"},
            {"doi": "10.1101/gr.2596504", "pmid": "15489334"},
            {"doi": "10.1016/j.cub.2006.07.020", "pmid": "16950109"},
            {"doi": "10.1021/pr060363j", "pmid": "17081065"},
            {"doi": "10.1074/jbc.M109.050930", "pmid": "20154091"},
            {"doi": "10.1186/1752-0509-5-17", "pmid": "21269460"},
            {"doi": "10.1038/nature10335", "pmid": "21822290"},
            {"doi": "10.1038/emboj.2012.16", "pmid": "22307087"},
            {"doi": "10.1111/j.1600-0854.2011.01294.x", "pmid": "21951725"},
            {"doi": "10.1021/pr300630k", "pmid": "23186163"},
            {"doi": "10.1002/pmic.201400617", "pmid": "25944712"},
            {"doi": "10.1073/pnas.1110415108", "pmid": "22065758"},
652
            {"doi": "10.7554/eLife.31012", "pmid": "29125462"},
Hervé  MENAGER's avatar
Hervé MENAGER committed
653
        ]
654
655
656
657
658
659
660
661

        def to_dict(d):
            return dict([(k["pmid"], k["doi"]) for k in d])

        def values_ignore_case(d):
            # after all case does not matter for URL and doi are URL
            return dict([(k, v.lower()) for k, v in d.items()])

662
        # also ignore the order of the key in the dict
663
664
        self.assertDictEqual(
            values_ignore_case(to_dict(resp["citations"])),
665
            values_ignore_case(to_dict(exp_citations)),
Hervé  MENAGER's avatar
Hervé MENAGER committed
666
667
668
669
        )
        exp_alternative_names = [
            {"full": "GTP-binding protein RAY"},
            {"full": "Ras-related protein Rab-1C"},
Bryan  BRANCOTTE's avatar
Bryan BRANCOTTE committed
670
        ]
Hervé  MENAGER's avatar
Hervé MENAGER committed
671
        self.assertEqual(
Hervé  MENAGER's avatar
Hervé MENAGER committed
672
673
674
            sorted(exp_alternative_names, key=lambda k: k["full"]),
            sorted(resp["alternative_names"], key=lambda k: k["full"]),
        )
675
676

    def test_get_uniprot_info_domains(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
677
678
679
        resp = get_uniprot_info("O00255")
        exp_domains = ["PF05053"]
        self.assertEqual(sorted(resp["domains"]), sorted(exp_domains))
680
681
682
683
684
685
686
687


class TestGetPDBUniProtMapping(TestCase):
    """
    Test retrieving protein for a PDB entry
    """

    def test_find_info(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
688
        target = sorted(["Q03164", "O00255"])
689
        resp = get_pdb_uniprot_mapping("3u85")
690
691
692
693
694
        resp = sorted(resp)
        self.assertEqual(resp, target)
        self.assertEqual(len(resp), len(set(resp)))

    def test_entry_not_found(self):
695
        self.assertRaises(EntryNotFoundError, get_pdb_uniprot_mapping, "Xu85")
696
697


698
699
700
701
702
703
class TestGetPDBPfamMapping(TestCase):
    """
    Test retrieving protein for a PDB entry
    """

    def test_find_info(self):
704
705
706
        target = {
            "PF05053": {"identifier": "Menin", "description": "Menin", "name": "Menin"}
        }
707
        resp = get_pdb_pfam_mapping("3u85")
708
709
710
        self.assertDictEqual(resp, target)

    def test_entry_not_found(self):
711
        self.assertRaises(EntryNotFoundError, get_pdb_pfam_mapping, "Xu85")
712
713


714
715
716
717
718
719
720
721
class TestConvertIUPACToSMILESAndMore(TestCase):
    """
    Test converting a IUPAC to smiles and inchi with opsin web service
    """

    def test_valid(self):
        pairs = [
            (
Hervé  MENAGER's avatar
Hervé MENAGER committed
722
                "2,4,6-trinitrotoluene",
723
                {
Hervé  MENAGER's avatar
Hervé MENAGER committed
724
                    "inchi": "InChI=1/C7H5N3O6/c1-4-6(9(13)14)2-5(8(11)12)"
Hervé  MENAGER's avatar
Hervé MENAGER committed
725
                    "3-7(4)10(15)16/h2-3H,1H3",
Hervé  MENAGER's avatar
Hervé MENAGER committed
726
                    "stdinchi": "InChI=1S/C7H5N3O6/c1-4-6(9(13)14)2-5(8(11)"
Hervé  MENAGER's avatar
Hervé MENAGER committed
727
                    "12)3-7(4)10(15)16/h2-3H,1H3",
728
                    "stdinchikey": "SPSSULHKWOKEEL-UHFFFAOYSA-N",
Hervé  MENAGER's avatar
Hervé MENAGER committed
729
                    "smiles": "[N+](=O)([O-])C1=C(C)C(=CC(=C1)[N+](=O)[O-])"
Hervé  MENAGER's avatar
Hervé MENAGER committed
730
731
                    "[N+](=O)[O-]",
                },
732
733
            ),
            (
Hervé  MENAGER's avatar
Hervé MENAGER committed
734
735
                "3-{1-oxo-6-[4-(piperidin-4-yl)butanamido]-2,3-dihydro-1H-"
                "isoindol-2-yl}propanoic acid",
736
                {
Hervé  MENAGER's avatar
Hervé MENAGER committed
737
                    "inchi": "InChI=1/C20H27N3O4/c24-18(3-1-2-14-6-9-21-10-"
Hervé  MENAGER's avatar
Hervé MENAGER committed
738
739
740
                    "7-14)22-16-5-4-15-13-23(11-8-19(25)26)20(27)17"
                    "(15)12-16/h4-5,12,14,21H,1-3,6-11,13H2,(H,22,"
                    "24)(H,25,26)/f/h22,25H",
Hervé  MENAGER's avatar
Hervé MENAGER committed
741
                    "stdinchi": "InChI=1S/C20H27N3O4/c24-18(3-1-2-14-6-9-21"
Hervé  MENAGER's avatar
Hervé MENAGER committed
742
743
744
                    "-10-7-14)22-16-5-4-15-13-23(11-8-19(25)26)"
                    "20(27)17(15)12-16/h4-5,12,14,21H,1-3,6-11,"
                    "13H2,(H,22,24)(H,25,26)",
745
                    "stdinchikey": "HWRXVANHVCXVHA-UHFFFAOYSA-N",
Hervé  MENAGER's avatar
Hervé MENAGER committed
746
747
748
                    "smiles": "O=C1N(CC2=CC=C(C=C12)NC(CCCC1CCNCC1)=O)CCC(=O)O",
                },
            ),
749
750
        ]
        for iupac, dict_expected in pairs:
751
            dict_returned = convert_iupac_to_smiles_and_inchi(iupac)
752
753
            self.assertEqual(dict_expected["smiles"], dict_returned["smiles"])
            self.assertEqual(dict_expected["inchi"], dict_returned["inchi"])
Hervé  MENAGER's avatar
Hervé MENAGER committed
754
755
            self.assertEqual(dict_expected["stdinchi"], dict_returned["stdinchi"])
            self.assertEqual(dict_expected["stdinchikey"], dict_returned["stdinchikey"])
756

757
    def test_invalid_entry(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
758
        self.assertRaises(
759
760
            EntryNotFoundError,
            convert_iupac_to_smiles_and_inchi,
Hervé  MENAGER's avatar
Hervé MENAGER committed
761
762
            "3-{1-oxo-6-[4-(piperid",
        )
Hervé  MENAGER's avatar
Hervé MENAGER committed
763
        self.assertRaises(EntryNotFoundError, convert_iupac_to_smiles_and_inchi, None)
764
765


766
767
768
769
770
771
772
773
774
class TestConvertSMILESToIUPAC(TestCase):
    """
    Test converting a smiles to IUPAC with cactus web service
    """

    def test_valid(self):
        smiles_to_iupacs = {"CCC": "propane"}
        for smiles, expected_iupac in smiles_to_iupacs.items():
            self.assertEqual(
775
                convert_smiles_to_iupac(smiles).lower(), expected_iupac.lower()
776
777
778
            )


779
780
class DuplicateGeneNameTestCase(TestCase):
    def test_works(self):
Hervé  MENAGER's avatar
Hervé MENAGER committed
781
782
        models.Protein.objects.get_or_create(uniprot_id="P12497")
        models.Protein.objects.get_or_create(uniprot_id="P0C6F2")