Skip to content

Commit

Permalink
chore: extending test coverage (#38) (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
gromdimon authored Sep 7, 2023
1 parent abd439d commit d5838f8
Show file tree
Hide file tree
Showing 46 changed files with 2,045 additions and 266 deletions.
1 change: 1 addition & 0 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pytest = "*"
pytest-asyncio = "*"
pytest-cov = "*"
pytest-httpx = "*"
pytest-subprocess = "*"
sphinx = "*"
sphinx-rtd-theme = "*"
starlette = "*"
Expand Down
370 changes: 189 additions & 181 deletions backend/Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
#: The REEV version from the file (``None`` if to load dynamically from git)
REEV_VERSION = None
# Try to obtain version from file, otherwise keep it at ``None``
if os.path.exists(VERSION_FILE):
if os.path.exists(VERSION_FILE): # pragma: no cover
with open(VERSION_FILE) as f:
REEV_VERSION = f.read().strip() or None

Expand Down
45 changes: 45 additions & 0 deletions backend/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,48 @@ async def test_invalid_proxy_route(monkeypatch, httpx_mock):
response = client.get("/proxy/some-other-path")
assert response.status_code == 404
assert response.text == "Reverse proxy route not found"


@pytest.mark.asyncio
async def test_version(monkeypatch):
"""Test version endpoint."""
monkeypatch.setattr(main, "REEV_VERSION", "1.2.3")
response = client.get("/version")
assert response.status_code == 200
assert response.text == "1.2.3"


@pytest.mark.asyncio
async def test_version_no_version(monkeypatch, fp):
"""Test version endpoint with no version."""
monkeypatch.setattr(main, "REEV_VERSION", None)
# We mock the output of ``git describe`` as subprocesses will be triggered
# internally.
fp.register(["git", "describe", "--tags", "--dirty"], stdout="v0.0.0-16-g7a4205d-dirty")
response = client.get("/version")

assert response.status_code == 200
assert response.text == "v0.0.0-16-g7a4205d-dirty"


@pytest.mark.asyncio
async def test_variantvalidator(monkeypatch, httpx_mock):
"""Test variant validator endpoint."""
variantvalidator_url = "https://rest.variantvalidator.org/VariantValidator/variantvalidator"
httpx_mock.add_response(
url=f"{variantvalidator_url}/{MOCKED_URL_TOKEN}",
method="GET",
text="Mocked response",
)

response = client.get(f"/variantvalidator/{MOCKED_URL_TOKEN}")
assert response.status_code == 200
assert response.text == "Mocked response"


@pytest.mark.asyncio
async def test_favicon():
"""Test favicon endpoint."""
response = client.get("/favicon.ico")
assert response.status_code == 200
assert response.headers["content-type"] == "image/vnd.microsoft.icon"
54 changes: 51 additions & 3 deletions frontend/src/api/__tests__/annonars.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import createFetchMock from 'vitest-fetch-mock'

import { AnnonarsClient } from '../annonars'
import * as BRCA1geneInfo from '@/assets/__tests__/BRCA1GeneInfo.json'
import * as BRCA1VariantInfo from '@/assets/__tests__/BRCA1VariantInfo.json'
import * as EMPSearchInfo from '@/assets/__tests__/EMPSearchInfo.json'

const fetchMocker = createFetchMock(vi)

describe('Annonars Client', () => {
describe.concurrent('Annonars Client', () => {
beforeEach(() => {
fetchMocker.enableMocks()
fetchMocker.resetMocks()
Expand All @@ -20,9 +22,9 @@ describe('Annonars Client', () => {
expect(JSON.stringify(result)).toEqual(JSON.stringify(BRCA1geneInfo))
})

it('fails to fetch gene info with wrong hgnc-id', async () => {
it('fails to fetch gene info with wrong HGNC id', async () => {
fetchMocker.mockResponse((req) => {
if (req.url.includes('hgnc-id=BRCA1')) {
if (req.url.includes('hgnc_id=BRCA1')) {
return Promise.resolve(JSON.stringify(BRCA1geneInfo))
}
return Promise.resolve(JSON.stringify({ status: 400 }))
Expand All @@ -32,4 +34,50 @@ describe('Annonars Client', () => {
const result = await client.fetchGeneInfo('123')
expect(JSON.stringify(result)).toEqual(JSON.stringify({ status: 400 }))
})

it('fetches variant info correctly', async () => {
fetchMocker.mockResponseOnce(JSON.stringify(BRCA1VariantInfo))

const client = new AnnonarsClient()
const result = await client.fetchVariantInfo('grch37', 'chr17', 43044295, 'A', 'G')
expect(JSON.stringify(result)).toEqual(JSON.stringify(BRCA1VariantInfo))
})

it('fails to fetch variant info with wrong variant', async () => {
fetchMocker.mockResponse((req) => {
if (req.url.includes('alternative=G')) {
return Promise.resolve(JSON.stringify(BRCA1VariantInfo))
}
return Promise.resolve(JSON.stringify({ status: 400 }))
})

const client = new AnnonarsClient()
const result = await client.fetchVariantInfo('grch37', 'chr17', 43044295, 'A', 'T')
expect(JSON.stringify(result)).toEqual(JSON.stringify({ status: 400 }))
})

it('fetches genes correctly', async () => {
fetchMocker.mockResponseOnce(JSON.stringify(EMPSearchInfo))

const client = new AnnonarsClient()
const result = await client.fetchGenes(
'q=BRCA1&fields=hgnc_id,ensembl_gene_id,ncbi_gene_id,symbol'
)
expect(JSON.stringify(result)).toEqual(JSON.stringify(EMPSearchInfo))
})

it('fails to fetch genes with wrong query', async () => {
fetchMocker.mockResponse((req) => {
if (req.url.includes('q=BRCA1')) {
return Promise.resolve(JSON.stringify(EMPSearchInfo))
}
return Promise.resolve(JSON.stringify({ status: 400 }))
})

const client = new AnnonarsClient()
const result = await client.fetchGenes(
'q=BRCA2&fields=hgnc_id,ensembl_gene_id,ncbi_gene_id,symbol'
)
expect(JSON.stringify(result)).toEqual(JSON.stringify({ status: 400 }))
})
})
10 changes: 8 additions & 2 deletions frontend/src/api/__tests__/common.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { describe, it, expect } from 'vitest'

import { API_BASE_PREFIX_ANNONARS, API_BASE_PREFIX_MEHARI } from '../common'
import { API_BASE_PREFIX, API_BASE_PREFIX_ANNONARS, API_BASE_PREFIX_MEHARI } from '../common'

describe.concurrent('API_BASE_PREFIX constants', () => {
it('returns the correct API base prefix in production mode', () => {
const originalMode = import.meta.env.MODE
expect(API_BASE_PREFIX).toBe('/')
import.meta.env.MODE = originalMode
})

describe('API_BASE_PREFIX constants', () => {
it('returns the correct API base prefix for annonars in production mode', () => {
const originalMode = import.meta.env.MODE
expect(API_BASE_PREFIX_ANNONARS).toBe('/proxy/annonars')
Expand Down
43 changes: 43 additions & 0 deletions frontend/src/api/__tests__/mehari.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { beforeEach, describe, it, expect, vi } from 'vitest'
import createFetchMock from 'vitest-fetch-mock'

import { MehariClient } from '../mehari'
import * as BRCA1TxInfo from '@/assets/__tests__/BRCA1TxInfo.json'

const fetchMocker = createFetchMock(vi)

describe.concurrent('Mehari Client', () => {
beforeEach(() => {
fetchMocker.enableMocks()
fetchMocker.resetMocks()
})

it('fetches TxCsq info correctly', async () => {
fetchMocker.mockResponseOnce(JSON.stringify(BRCA1TxInfo))

const client = new MehariClient()
const result = await client.retrieveTxCsq('grch37', 'chr17', 43044295, 'A', 'G', 'HGNC:1100')
expect(JSON.stringify(result)).toEqual(JSON.stringify(BRCA1TxInfo))
})

it('fetches TxCsq info correctly without HGNC id', async () => {
fetchMocker.mockResponseOnce(JSON.stringify(BRCA1TxInfo))

const client = new MehariClient()
const result = await client.retrieveTxCsq('grch37', 'chr17', 43044295, 'A', 'G')
expect(JSON.stringify(result)).toEqual(JSON.stringify(BRCA1TxInfo))
})

it('fails to fetch variant info with wrong variant', async () => {
fetchMocker.mockResponse((req) => {
if (req.url.includes('alternative=G')) {
return Promise.resolve(JSON.stringify(BRCA1TxInfo))
}
return Promise.resolve(JSON.stringify({ status: 400 }))
})

const client = new MehariClient()
const result = await client.retrieveTxCsq('grch37', 'chr17', 43044295, 'A', 'T')
expect(JSON.stringify(result)).toEqual(JSON.stringify({ status: 400 }))
})
})
2 changes: 1 addition & 1 deletion frontend/src/api/__tests__/misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MiscClient } from '../misc'

const fetchMocker = createFetchMock(vi)

describe('Misc Client', () => {
describe.concurrent('Misc Client', () => {
beforeEach(() => {
fetchMocker.enableMocks()
fetchMocker.resetMocks()
Expand Down
121 changes: 118 additions & 3 deletions frontend/src/api/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { describe, it, expect } from 'vitest'

import { roundIt, search } from '../utils'
import {
roundIt,
separateIt,
isVariantMt,
isVariantMtHomopolymer,
search,
infoFromQuery
} from '../utils'

describe('roundIt method', () => {
describe.concurrent('roundIt method', () => {
it('should round a positive value with default digits', () => {
const result = roundIt(3.14159)
expect(result).toBe('<abbr title="3.14159">3.14</abbr>')
Expand Down Expand Up @@ -34,8 +41,70 @@ describe('roundIt method', () => {
})
})

describe('separateIt method', () => {
it('should separate a positive value with default separator', () => {
const result = separateIt(123456789)
expect(result).toBe(' 123 456 789 ')
})

it('should separate a positive value with specified separator', () => {
const result = separateIt(123456789, ',')
expect(result).toBe(',123,456,789,')
})

it('should handle zero value', () => {
const result = separateIt(0)
expect(result).toBe('0 ')
})

it('should handle float value', () => {
const result = separateIt(123456789.12345)
expect(result).toBe(' 123 456 789 ')
})
})

describe('isVariantMt method', () => {
it('should return true if mitochondrial chromosome', () => {
const result_MT = isVariantMt({ chromosome: 'MT' })
const result_M = isVariantMt({ chromosome: 'M' })
const result_chrMT = isVariantMt({ chromosome: 'chrMT' })
const result_chrM = isVariantMt({ chromosome: 'chrM' })
expect(result_MT).toBe(true)
expect(result_M).toBe(true)
expect(result_chrMT).toBe(true)
expect(result_chrM).toBe(true)
})

it('should return false if not mitochondrial chromosome', () => {
const result = isVariantMt({ chromosome: '1' })
expect(result).toBe(false)
})
})

describe('isVariantMtHomopolymer method', () => {
it('should return true if mitochondrial homopolymer', () => {
const result = isVariantMtHomopolymer({ chromosome: 'MT', start: 70 })
expect(result).toBe(true)
})

it('should return false if not mitochondrial homopolymer (chromosome)', () => {
const result = isVariantMtHomopolymer({ chromosome: '1', start: 70 })
expect(result).toBe(false)
})

it('should return false if not mitochondrial homopolymer (position)', () => {
const result = isVariantMtHomopolymer({ chromosome: 'MT', start: 1 })
expect(result).toBe(false)
})

it('should return false for NaN', () => {
const result = isVariantMtHomopolymer(NaN)
expect(result).toBe(false)
})
})

describe('search method', () => {
it('should return route location if match', () => {
it('should return "gene" route location for HGNC queries', () => {
const result = search('HGNC:1100', 'ghcr37')
expect(result).toEqual({
name: 'gene',
Expand All @@ -46,6 +115,52 @@ describe('search method', () => {
})
})

it('should return "variant" route location for Variant queries', () => {
const result = search('chr37:12345:A:G', 'ghcr37')
expect(result).toEqual({
name: 'variant',
params: {
searchTerm: 'chr37:12345:A:G',
genomeRelease: 'ghcr37'
}
})
})

it('should return "genes" route location for general queries', () => {
const result = search('TP53', 'ghcr37')
expect(result).toEqual({
name: 'genes',
query: {
q: 'TP53',
fields: 'hgnc_id,ensembl_gene_id,ncbi_gene_id,symbol'
}
})
})
})

describe('infoFromQuery method', () => {
it('should return info from query', () => {
const result = infoFromQuery('chr37:12345:A:G')
expect(result).toEqual({
chromosome: 'chr37',
pos: '12345',
reference: 'A',
alternative: 'G',
hgnc_id: undefined
})
})

it('should return empty object if no query', () => {
const result = infoFromQuery('')
expect(result).toEqual({
chromosome: '',
pos: undefined,
reference: undefined,
alternative: undefined,
hgnc_id: undefined
})
})

it('should return null if no entry', () => {
const result = search('', 'foo37')
expect(result).toBe(null)
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const isVariantMt = (smallVar: any): boolean => {
* @param smallVar Small variant to check.
* @returns whether the position is in a mitochondrial homopolymer
*/
export const isVariantMtHomopolymer = (smallVar: any): any => {
export const isVariantMtHomopolymer = (smallVar: any): boolean => {
if (!smallVar) {
return false
}
Expand All @@ -75,6 +75,8 @@ export const isVariantMtHomopolymer = (smallVar: any): any => {
}
if (isVariantMt(smallVar)) {
return positionCheck(start) || positionCheck(end)
} else {
return false
}
}

Expand Down Expand Up @@ -141,7 +143,6 @@ export const search = (searchTerm: string, genomeRelease: string) => {
return routeLocation
}
}
return null
}

/**
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/assets/__tests__/BRCA1TxInfo.json
Git LFS file not shown
3 changes: 3 additions & 0 deletions frontend/src/assets/__tests__/BRCA1VariantInfo.json
Git LFS file not shown
3 changes: 3 additions & 0 deletions frontend/src/assets/__tests__/BRCA1VariantValidator.json
Git LFS file not shown
3 changes: 3 additions & 0 deletions frontend/src/assets/__tests__/EMPSearchInfo.json
Git LFS file not shown
Loading

0 comments on commit d5838f8

Please sign in to comment.