Example #1
0
    def test_query_variants(self, mock_get_variants, mock_get_gene_counts,
                            mock_error_logger, mock_analyst_group):
        url = reverse(query_variants_handler, args=['abc'])
        self.check_collaborator_login(
            url, request_data={'projectFamilies': PROJECT_FAMILIES})
        url = reverse(query_variants_handler, args=[SEARCH_HASH])

        # add a locus list
        LocusList.objects.get(guid=LOCUS_LIST_GUID).projects.add(
            Project.objects.get(guid=PROJECT_GUID))

        # Test invalid inputs
        response = self.client.get(url)
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.reason_phrase,
                         'Invalid search hash: {}'.format(SEARCH_HASH))
        mock_error_logger.assert_not_called()

        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({'search': SEARCH}))
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.reason_phrase,
                         'Invalid search: no projects/ families specified')
        mock_error_logger.assert_not_called()

        mock_get_variants.side_effect = InvalidIndexException('Invalid index')
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json()['error'], 'Invalid index')
        mock_error_logger.assert_called_with('Invalid index', extra=mock.ANY)

        mock_get_variants.side_effect = InvalidSearchException(
            'Invalid search')
        mock_error_logger.reset_mock()
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.json()['error'], 'Invalid search')
        mock_error_logger.assert_not_called()

        mock_get_variants.side_effect = ConnectionTimeout(
            '', '', ValueError('Timeout'))
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 504)
        self.assertEqual(response.json()['error'],
                         'ConnectionTimeout caused by - ValueError(Timeout)')
        mock_error_logger.assert_not_called()

        mock_get_variants.side_effect = TransportError(
            'N/A', 'search_phase_execution_exception', {'error': 'Invalid'})
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 400)
        self.assertEqual(
            response.json()['error'],
            "TransportError: N/A - 'search_phase_execution_exception' - 'Invalid'"
        )
        self.assertEqual(response.json()['detail'], {'error': 'Invalid'})
        mock_error_logger.assert_not_called()

        error_info_json = {
            'error': {
                'root_cause': [{
                    'type':
                    'response_handler_failure_transport_exception'
                }]
            }
        }
        mock_get_variants.side_effect = TransportError(
            '401', 'search_phase_execution_exception', error_info_json)
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 401)
        self.assertEqual(
            response.json()['error'],
            "TransportError: 401 - 'search_phase_execution_exception' - response_handler_failure_transport_exception"
        )
        self.assertEqual(response.json()['detail'], error_info_json)
        mock_error_logger.assert_not_called()

        mock_get_variants.side_effect = _get_es_variants

        # Test new search
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertSetEqual(
            set(response_json.keys()), {
                'searchedVariants', 'savedVariantsByGuid', 'genesById',
                'search', 'variantTagsByGuid', 'variantNotesByGuid',
                'variantFunctionalDataByGuid', 'locusListsByGuid'
            })
        self.assertListEqual(response_json['searchedVariants'], VARIANTS)
        self.assertDictEqual(
            response_json['search'], {
                'search':
                SEARCH,
                'projectFamilies': [{
                    'projectGuid': PROJECT_GUID,
                    'familyGuids': mock.ANY
                }],
                'totalResults':
                3,
            })
        self.assertSetEqual(
            set(response_json['search']['projectFamilies'][0]['familyGuids']),
            {'F000001_1', 'F000002_2'})
        self.assertSetEqual(set(response_json['savedVariantsByGuid'].keys()), {
            'SV0000001_2103343353_r0390_100', 'SV0000002_1248367227_r0390_100'
        })
        self.assertSetEqual(
            set(response_json['genesById'].keys()),
            {'ENSG00000227232', 'ENSG00000268903', 'ENSG00000233653'})
        gene_fields = {'locusListGuids'}
        gene_fields.update(GENE_VARIANT_FIELDS)
        self.assertSetEqual(
            set(response_json['genesById']['ENSG00000227232'].keys()),
            gene_fields)
        self.assertListEqual(
            response_json['genesById']['ENSG00000227232']['locusListGuids'],
            [LOCUS_LIST_GUID])
        self.assertSetEqual(set(response_json['locusListsByGuid'].keys()),
                            {LOCUS_LIST_GUID})
        intervals = response_json['locusListsByGuid'][LOCUS_LIST_GUID][
            'intervals']
        self.assertEqual(len(intervals), 2)
        self.assertSetEqual(
            set(intervals[0].keys()), {
                'locusListGuid', 'locusListIntervalGuid', 'genomeVersion',
                'chrom', 'start', 'end'
            })

        results_model = VariantSearchResults.objects.get(
            search_hash=SEARCH_HASH)
        mock_get_variants.assert_called_with(results_model,
                                             sort='xpos',
                                             page=1,
                                             num_results=100,
                                             skip_genotype_filter=False)
        mock_error_logger.assert_not_called()

        # Test pagination
        response = self.client.get('{}?page=3'.format(url))
        self.assertEqual(response.status_code, 200)
        mock_get_variants.assert_called_with(results_model,
                                             sort='xpos',
                                             page=3,
                                             num_results=100,
                                             skip_genotype_filter=False)
        mock_error_logger.assert_not_called()

        # Test sort
        response = self.client.get('{}?sort=pathogenicity'.format(url))
        self.assertEqual(response.status_code, 200)
        mock_get_variants.assert_called_with(results_model,
                                             sort='pathogenicity',
                                             page=1,
                                             num_results=100,
                                             skip_genotype_filter=False)
        mock_error_logger.assert_not_called()

        # Test export
        export_url = reverse(export_variants_handler, args=[SEARCH_HASH])
        response = self.client.get(export_url)
        self.assertEqual(response.status_code, 200)
        expected_content = [
            [
                'chrom', 'pos', 'ref', 'alt', 'gene', 'worst_consequence',
                '1kg_freq', 'exac_freq', 'gnomad_genomes_freq',
                'gnomad_exomes_freq', 'topmed_freq', 'cadd', 'revel', 'eigen',
                'polyphen', 'sift', 'muttaster', 'fathmm', 'rsid', 'hgvsc',
                'hgvsp', 'clinvar_clinical_significance', 'clinvar_gold_stars',
                'filter', 'family_id_1', 'tags_1', 'notes_1', 'family_id_2',
                'tags_2', 'notes_2', 'sample_1', 'num_alt_alleles_1', 'gq_1',
                'ab_1', 'sample_2', 'num_alt_alleles_2', 'gq_2', 'ab_2'
            ],
            [
                '21', '3343400', 'GAGA', 'G', 'WASH7P', 'missense_variant', '',
                '', '', '', '', '', '', '', '', '', '', '', '',
                'ENST00000623083.3:c.1075G>A', 'ENSP00000485442.1:p.Gly359Ser',
                '', '', '', '1',
                'Tier 1 - Novel gene and phenotype (None)|Review (None)', '',
                '2', '', '', 'NA19675', '1', '46.0', '0.702127659574',
                'NA19679', '0', '99.0', '0.0'
            ],
            [
                '3', '835', 'AAAG', 'A', '', '', '', '', '', '', '', '', '',
                '', '', '', '', '', '', '', '', '', '', '', '1', '', '', '',
                '', '', 'NA19679', '0', '99.0', '0.0', '', '', '', ''
            ],
            [
                '12', '48367227', 'TC', 'T', '', '', '', '', '', '', '', '',
                '', '', '', '', '', '', '', '', '', '', '', '', '2',
                'Known gene for phenotype (None)|Excluded (None)',
                'test n\xf8te (None)', '', '', '', '', '', '', '', '', '', '',
                ''
            ]
        ]
        self.assertEqual(
            response.content,
            ('\n'.join(['\t'.join(line)
                        for line in expected_content]) + '\n').encode('utf-8'))

        mock_get_variants.assert_called_with(results_model,
                                             page=1,
                                             load_all=True)
        mock_error_logger.assert_not_called()

        # Test gene breakdown
        gene_counts = {
            'ENSG00000227232': {
                'total': 2,
                'families': {
                    'F000001_1': 2,
                    'F000002_2': 1
                }
            },
            'ENSG00000268903': {
                'total': 1,
                'families': {
                    'F000002_2': 1
                }
            }
        }
        mock_get_gene_counts.return_value = gene_counts

        gene_breakdown_url = reverse(get_variant_gene_breakdown,
                                     args=[SEARCH_HASH])
        response = self.client.get(gene_breakdown_url)
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertSetEqual(set(response_json.keys()),
                            {'searchGeneBreakdown', 'genesById'})
        self.assertDictEqual(response_json['searchGeneBreakdown'],
                             {SEARCH_HASH: gene_counts})
        self.assertSetEqual(set(response_json['genesById'].keys()),
                            {'ENSG00000227232', 'ENSG00000268903'})
        gene_fields = {
            'constraints', 'omimPhenotypes', 'mimNumber', 'cnSensitivity'
        }
        gene_fields.update(GENE_FIELDS)
        self.assertSetEqual(
            set(response_json['genesById']['ENSG00000227232'].keys()),
            gene_fields)

        # Test compound hets
        mock_get_variants.side_effect = _get_compound_het_es_variants
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertSetEqual(
            set(response_json.keys()), {
                'searchedVariants', 'savedVariantsByGuid', 'genesById',
                'search', 'variantTagsByGuid', 'variantNotesByGuid',
                'variantFunctionalDataByGuid', 'locusListsByGuid'
            })
        self.assertListEqual(response_json['searchedVariants'],
                             COMP_HET_VARAINTS)
        self.assertSetEqual(set(response_json['savedVariantsByGuid'].keys()),
                            {'SV0000002_1248367227_r0390_100'})
        self.assertSetEqual(set(response_json['genesById'].keys()),
                            {'ENSG00000233653'})
        mock_error_logger.assert_not_called()

        # Test cross-project discovery for analyst users
        self.login_analyst_user()
        mock_get_variants.side_effect = _get_es_variants
        response = self.client.get('{}?sort=pathogenicity'.format(url))
        self.assertEqual(response.status_code, 403)

        mock_analyst_group.__bool__.return_value = True
        mock_analyst_group.resolve_expression.return_value = 'analysts'
        response = self.client.get('{}?sort=pathogenicity'.format(url))
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertSetEqual(
            set(response_json.keys()), {
                'searchedVariants', 'savedVariantsByGuid', 'genesById',
                'search', 'variantTagsByGuid', 'variantNotesByGuid',
                'variantFunctionalDataByGuid', 'familiesByGuid',
                'locusListsByGuid'
            })

        self.assertListEqual(response_json['searchedVariants'],
                             VARIANTS_WITH_DISCOVERY_TAGS)
        self.assertSetEqual(set(response_json['familiesByGuid'].keys()),
                            {'F000011_11'})
        mock_get_variants.assert_called_with(results_model,
                                             sort='pathogenicity_hgmd',
                                             page=1,
                                             num_results=100,
                                             skip_genotype_filter=False)
        mock_error_logger.assert_not_called()

        # Test no results
        mock_get_variants.side_effect = _get_empty_es_variants
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertDictEqual(
            response_json, {
                'searchedVariants': [],
                'search': {
                    'search': SEARCH,
                    'projectFamilies': PROJECT_FAMILIES,
                    'totalResults': 0,
                }
            })
        mock_error_logger.assert_not_called()
    def test_query_variants(self, mock_get_variants, mock_get_gene_counts):
        url = reverse(query_variants_handler, args=[SEARCH_HASH])
        _check_login(self, url)

        # add a locus list
        response = self.client.post(reverse(add_project_locus_lists,
                                            args=[PROJECT_GUID]),
                                    content_type='application/json',
                                    data=json.dumps(
                                        {'locusListGuids': [LOCUS_LIST_GUID]}))
        self.assertEqual(response.status_code, 200)

        # Test invalid inputs
        response = self.client.get(url)
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.reason_phrase,
                         'Invalid search hash: {}'.format(SEARCH_HASH))

        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({'search': SEARCH}))
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.reason_phrase,
                         'Invalid search: no projects/ families specified')

        mock_get_variants.side_effect = InvalidIndexException('Invalid index')
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 400)
        self.assertEqual(response.reason_phrase, 'Invalid index')

        mock_get_variants.side_effect = _get_es_variants

        # Test new search
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertSetEqual(
            set(response_json.keys()), {
                'searchedVariants', 'savedVariantsByGuid', 'genesById',
                'search', 'variantTagsByGuid', 'variantNotesByGuid',
                'variantFunctionalDataByGuid', 'familiesByGuid'
            })

        self.assertListEqual(response_json['searchedVariants'],
                             EXPECTED_VARIANTS_WITH_DISCOVERY_TAGS)
        self.assertDictEqual(
            response_json['search'], {
                'search': SEARCH,
                'projectFamilies': PROJECT_FAMILIES,
                'totalResults': 3,
            })
        self.assertSetEqual(set(response_json['savedVariantsByGuid'].keys()), {
            'SV0000001_2103343353_r0390_100', 'SV0000002_1248367227_r0390_100'
        })
        self.assertSetEqual(
            set(response_json['genesById'].keys()),
            {'ENSG00000227232', 'ENSG00000268903', 'ENSG00000233653'})
        self.assertListEqual(
            response_json['genesById']['ENSG00000227232']['locusListGuids'],
            [LOCUS_LIST_GUID])
        self.assertSetEqual(set(response_json['familiesByGuid'].keys()),
                            {'F000011_11'})

        results_model = VariantSearchResults.objects.get(
            search_hash=SEARCH_HASH)
        mock_get_variants.assert_called_with(results_model,
                                             sort='xpos',
                                             page=1,
                                             num_results=100)

        # Test pagination
        response = self.client.get('{}?page=3'.format(url))
        self.assertEqual(response.status_code, 200)
        mock_get_variants.assert_called_with(results_model,
                                             sort='xpos',
                                             page=3,
                                             num_results=100)

        # Test sort
        response = self.client.get('{}?sort=consequence'.format(url))
        self.assertEqual(response.status_code, 200)
        mock_get_variants.assert_called_with(results_model,
                                             sort='consequence',
                                             page=1,
                                             num_results=100)

        # Test export
        export_url = reverse(export_variants_handler, args=[SEARCH_HASH])
        response = self.client.get(export_url)
        self.assertEqual(response.status_code, 200)
        export_content = [
            row.split('\t')
            for row in response.content.rstrip('\n').split('\n')
        ]
        self.assertEqual(len(export_content), 4)
        self.assertListEqual(export_content[0], [
            'chrom', 'pos', 'ref', 'alt', 'gene', 'worst_consequence',
            '1kg_freq', 'exac_freq', 'gnomad_genomes_freq',
            'gnomad_exomes_freq', 'topmed_freq', 'cadd', 'revel', 'eigen',
            'polyphen', 'sift', 'muttaster', 'fathmm', 'rsid', 'hgvsc',
            'hgvsp', 'clinvar_clinical_significance', 'clinvar_gold_stars',
            'filter', 'family_id_1', 'tags_1', 'notes_1', 'family_id_2',
            'tags_2', 'notes_2', 'sample_1:num_alt_alleles:gq:ab',
            'sample_2:num_alt_alleles:gq:ab'
        ])
        self.assertListEqual(export_content[1], [
            '21', '3343400', 'GAGA', 'G', 'WASH7P', 'missense_variant', '', '',
            '', '', '', '', '', '', '', '', '', '', '',
            'ENST00000623083.3:c.1075G>A', 'ENSP00000485442.1:p.Gly359Ser', '',
            '', '', '1',
            'Tier 1 - Novel gene and phenotype (None)|Review (None)', '', '2',
            '', '', 'NA19675:1:46.0:0.702127659574', 'NA19679:0:99.0:0.0'
        ])
        self.assertListEqual(export_content[3], [
            '12', '48367227', 'TC', 'T', '', '', '', '', '', '', '', '', '',
            '', '', '', '', '', '', '', '', '', '', '', '2', 'Review (None)',
            'test n\xc3\xb8te (None)', '', '', '', '', ''
        ])

        mock_get_variants.assert_called_with(results_model,
                                             page=1,
                                             load_all=True)

        # Test gene breakdown
        gene_counts = {
            'ENSG00000227232': {
                'total': 2,
                'families': {
                    'F000001_1': 2,
                    'F000002_2': 1
                }
            },
            'ENSG00000268903': {
                'total': 1,
                'families': {
                    'F000002_2': 1
                }
            }
        }
        mock_get_gene_counts.return_value = gene_counts

        gene_breakdown_url = reverse(get_variant_gene_breakdown,
                                     args=[SEARCH_HASH])
        response = self.client.get(gene_breakdown_url)
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertSetEqual(set(response_json.keys()),
                            {'searchGeneBreakdown', 'genesById'})
        self.assertDictEqual(response_json['searchGeneBreakdown'],
                             {SEARCH_HASH: gene_counts})
        self.assertSetEqual(set(response_json['genesById'].keys()),
                            {'ENSG00000227232', 'ENSG00000268903'})

        # Test no cross-project discovery for non-staff users
        login_non_staff_user(self)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertSetEqual(
            set(response_json.keys()), {
                'searchedVariants', 'savedVariantsByGuid', 'genesById',
                'search', 'variantTagsByGuid', 'variantNotesByGuid',
                'variantFunctionalDataByGuid'
            })
        self.assertListEqual(response_json['searchedVariants'],
                             EXPECTED_VARIANTS)

        # Test no results
        mock_get_variants.side_effect = _get_empty_es_variants
        response = self.client.post(url,
                                    content_type='application/json',
                                    data=json.dumps({
                                        'projectFamilies': PROJECT_FAMILIES,
                                        'search': SEARCH
                                    }))
        self.assertEqual(response.status_code, 200)
        response_json = response.json()
        self.assertDictEqual(
            response_json, {
                'searchedVariants': [],
                'genesById': {},
                'search': {
                    'search': SEARCH,
                    'projectFamilies': PROJECT_FAMILIES,
                    'totalResults': 0,
                }
            })