Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gromdimon committed Sep 13, 2023
1 parent 5695102 commit 2cf691e
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 87 deletions.
13 changes: 9 additions & 4 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
"bp5": False,
"bp6": False,
"bp7": False,
"class_override": None,
}

app = FastAPI()
Expand Down Expand Up @@ -158,7 +157,7 @@ async def variantvalidator(request: Request, path: str):

# Register app for retrieving ACMG classification.
@app.get("/acmg/{path:path}")
async def acmg(request: Request, path: str):
async def acmg(request: Request):
"""Implement searching for ACMG classification."""
query_params = request.query_params
chromosome = query_params.get("chromosome")
Expand All @@ -167,14 +166,20 @@ async def acmg(request: Request, path: str):
alternative = query_params.get("alternative")
build = query_params.get("release")

url = f"http://wintervar.wglab.org/api_new.php?queryType=position&chr={chromosome}&pos={position}&ref={reference}&alt={alternative}&build={build}"
if not chromosome or not position or not reference or not alternative or not build:
return Response(status_code=400, content="Missing query parameters")

url = (
f"http://wintervar.wglab.org/api_new.php?"
f"queryType=position&chr={chromosome}&pos={position}"
f"&ref={reference}&alt={alternative}&build={build}"
)
backend_req = client.build_request(method="GET", url=url)
backend_resp = await client.send(backend_req)
if backend_resp.status_code != 200:
return Response(status_code=backend_resp.status_code, content=backend_resp.content)

acmg_rating = ACMG_RATING.copy()
acmg_rating["class_override"] = query_params.get("class_override", None)
for key, value in backend_resp.json().items():
if key.lower() in acmg_rating:
acmg_rating[key.lower()] = True if value == 1 else False
Expand Down
27 changes: 26 additions & 1 deletion backend/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ async def test_version_no_version(monkeypatch, fp):


@pytest.mark.asyncio
async def test_variantvalidator(monkeypatch, httpx_mock):
async def test_variantvalidator(httpx_mock):
"""Test variant validator endpoint."""
variantvalidator_url = "https://rest.variantvalidator.org/VariantValidator/variantvalidator"
httpx_mock.add_response(
Expand All @@ -113,6 +113,31 @@ async def test_variantvalidator(monkeypatch, httpx_mock):
assert response.text == "Mocked response"


@pytest.mark.asyncio
async def test_acmg(httpx_mock):
"""Test ACMG endpoint."""
acmg_url = "http://wintervar.wglab.org/api_new.php"
acmg_qury_params = "?chromosome=1&position=123&reference=A&alternative=T&release=hg19"
httpx_mock.add_response(
url=f"{acmg_url}?queryType=position&chr=1&pos=123&ref=A&alt=T&build=hg19",
method="GET",
json={"acmg": "Mocked response"},
)

response = client.get(f"/acmg/{acmg_qury_params}")
assert response.status_code == 200
print("Main", main.ACMG_RATING)
assert response.json() == main.ACMG_RATING


@pytest.mark.asyncio
async def test_acmg_missing_query_params():
"""Test ACMG endpoint with missing query parameters."""
response = client.get("/acmg")
assert response.status_code == 400
assert response.text == "Missing query parameters"


@pytest.mark.asyncio
async def test_favicon():
"""Test favicon endpoint."""
Expand Down
59 changes: 14 additions & 45 deletions frontend/src/components/VariantDetails/AcmgRating.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ const emptyAcmgRatingTemplate: any = {
bp4: false,
bp5: false,
bp6: false,
bp7: false,
class_override: null
bp7: false
}
const acmgRatingToSubmit = ref({ ...emptyAcmgRatingTemplate })
Expand Down Expand Up @@ -79,7 +78,6 @@ const resetAcmgRating = () => {
acmgRatingToSubmit.value.bp5 = acmgRatingStore.acmgRating.bp5
acmgRatingToSubmit.value.bp6 = acmgRatingStore.acmgRating.bp6
acmgRatingToSubmit.value.bp7 = acmgRatingStore.acmgRating.bp7
acmgRatingToSubmit.value.class_override = acmgRatingStore.acmgRating.class_override
} else {
unsetAcmgRating()
}
Expand Down Expand Up @@ -158,14 +156,6 @@ const calculateAcmgRating = computed(() => {
return computedClassAuto
})
const convertEmptyToNull = (classOverride: any) => {
if (classOverride === '' || classOverride === null) {
acmgRatingToSubmit.value.class_override = null
} else {
acmgRatingToSubmit.value.class_override = classOverride
}
}
const onSubmitAcmgRating = async () => {
await acmgRatingStore.submitAcmgRating(props.smallVariant, acmgRatingToSubmit.value)
}
Expand Down Expand Up @@ -249,7 +239,7 @@ const acmgCriteriaInfo = {
id: 'pm2',
description:
'Absent from controls (or at extremely low frequency if recessive) in Exome Sequencing Project, 1000 Genomes Project, or Exome Aggregation Consortium',
hint: 'rare in 1:20.000 in ExAC'
hint: 'rare; < 1:20.000 in ExAC'
},
{
name: 'PM3',
Expand Down Expand Up @@ -301,7 +291,7 @@ const acmgCriteriaInfo = {
id: 'pp3',
description:
'Multiple lines of computational evidence support a deleterious effect on the gene or gene product (conservation, evolutionary, splicing impact, etc.)',
hint: 'predicted pathogenic'
hint: 'predicted pathogenic >= 2'
},
{
name: 'PP4',
Expand Down Expand Up @@ -386,34 +376,34 @@ const acmgCriteriaInfo = {
id: 'bp3',
description:
'In-frame deletions/insertions in a repetitive region without a known function',
hint: 'in-frame del/ins in repeat'
hint: 'in-frame indel in repeat'
},
{
name: 'BP4',
id: 'bp4',
description:
'Multiple lines of computational evidence suggest no impact on gene or gene product (conservation, evolutionary,splicing impact, etc.)',
hint: 'predicted benign'
hint: 'prediction: benign'
},
{
name: 'BP5',
id: 'bp5',
description: 'Variant found in a case with an alternate molecular basis for disease',
hint: 'other variant is causative'
hint: 'different gene in other case'
},
{
name: 'BP6',
id: 'bp6',
description:
'Reputable source recently reports variant as benign, but the evidence is not available to the laboratory to perform an independent evaluation',
hint: 'reliable source: benign'
hint: 'reputable source: benign'
},
{
name: 'BP7',
id: 'bp7',
description:
'A synonymous (silent) variant for which splicing prediction algorithms predict no impact to the splice consensus sequence nor the creation of a new splice site AND the nucleotide is not highly conserved',
hint: 'synonymous: no splice effect'
hint: 'silent, no splicing/conservation'
}
]
}
Expand Down Expand Up @@ -482,21 +472,6 @@ const acmgCriteriaInfo = {
{{ calculateAcmgRating }}
</h1>
</div>
<div title="Manually override the automatically determined class">
<div>
<label for="acmg-class-override"><strong>ACMG class override</strong></label>
</div>
<div style="margin-bottom: 12px">
<v-text-field
label="Label"
variant="outlined"
id="acmg-class-override"
style="width: 135px; height: 50px"
@change="convertEmptyToNull(acmgRatingToSubmit.class_override)"
v-model.number="acmgRatingToSubmit.class_override"
/>
</div>
</div>
<div>
<div>
<label for="acmg-class"><strong>Score explanation:</strong></label>
Expand All @@ -522,9 +497,7 @@ const acmgCriteriaInfo = {
<div class="button-group">
<v-btn @click="unsetAcmgRating()"> Clear </v-btn>
<v-btn @click="resetAcmgRating()"> Reset </v-btn>
<v-btn prepend-icon="mdi-star-check" @click="onSubmitAcmgRating()">
Submit to ClinVar
</v-btn>
<v-btn prepend-icon="mdi-star-check" @click="onSubmitAcmgRating()"> Save changes </v-btn>
</div>
<div v-if="acmgRatingConflicting">
<div>
Expand All @@ -539,15 +512,11 @@ const acmgCriteriaInfo = {
<div>
<v-icon>mdi-information</v-icon>
Select all fulfilled criteria to get the classification following Richards
<i>et al.</i> (2015). If necessary, you can also specify a manual override.
<span class="badge badge-primary">Submit</span> indicates that there are changes not yet
submitted, while <span class="badge badge-success">Submit</span> indicates that changes
have been submitted or not made at all.
<span class="badge badge-warning">Submit</span> indicates that there are conflicting
variant interpretations. In that case, submission is possible, but not recommended.
Press <span class="badge badge-secondary">Reset</span> to reset the form to the last
submitted state. Press <span class="badge badge-secondary">Clear</span> and
<span class="badge badge-primary">Submit</span> to delete ACMG rating.
<i>et al.</i> (2015). If necessary, you can also specify a manual override. Press
<span style="background-color: wheat">Reset</span> to reset the form to the last
submitted state. Press <span style="background-color: wheat">Clear</span> to delete ACMG
rating and <span style="background-color: wheat">Submit</span> to submit rating to
ClinVar.
</div>
</div>
</div>
Expand Down
106 changes: 106 additions & 0 deletions frontend/src/components/__tests__/AcmgRating.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { describe, expect, it, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import { routes } from '@/router'

import { createTestingPinia } from '@pinia/testing'
import { useVariantAcmgRatingStore } from '@/stores/variantAcmgRating'
import { StoreState } from '@/stores/misc'

import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'

import AcmgRating from '@/components/VariantDetails/AcmgRating.vue'

const vuetify = createVuetify({
components,
directives
})

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes
})
// Mock router push
router.push = vi.fn()

const smallVariantInfo = {
release: 'grch37',
chromosome: 'chr17',
start: '43044295',
end: '43044295',
reference: 'G',
alternative: 'A',
hgnc_id: 'HGNC:1100'
}

export const AcmgRatingInfo = {
pvs1: false,
ps1: false,
ps2: false,
ps3: false,
ps4: false,
pm1: false,
pm2: false,
pm3: false,
pm4: false,
pm5: false,
pm6: false,
pp1: false,
pp2: false,
pp3: false,
pp4: false,
pp5: false,
ba1: false,
bs1: false,
bs2: false,
bs3: false,
bs4: false,
bp1: false,
bp2: false,
bp3: false,
bp4: false,
bp5: false,
bp6: false,
bp7: false
}

const makeWrapper = () => {
const pinia = createTestingPinia({ createSpy: vi.fn() })
const store = useVariantAcmgRatingStore(pinia)

const mockRetrieveAcmgRating = vi.fn().mockImplementation(async () => {
store.storeState = StoreState.Active
store.smallVariant = JSON.parse(JSON.stringify(smallVariantInfo))
store.acmgRating = JSON.parse(JSON.stringify(AcmgRatingInfo))
})
store.retrieveAcmgRating = mockRetrieveAcmgRating

store.storeState = StoreState.Active
store.smallVariant = JSON.parse(JSON.stringify(smallVariantInfo))
store.acmgRating = JSON.parse(JSON.stringify(AcmgRatingInfo))

return mount(AcmgRating, {
props: {
smallVariant: smallVariantInfo
},
global: {
plugins: [vuetify, router, pinia],
components: {
AcmgRating
}
}
})
}

describe.concurrent('AcmgRating', async () => {
it('renders the AcmgRating info', async () => {
const wrapper = makeWrapper()
expect(wrapper.text()).toContain('Pathogenic')
expect(wrapper.text()).toContain('Benign')

const switchers = wrapper.findAll('.v-switch')
expect(switchers.length).toBe(28)
})
})
Loading

0 comments on commit 2cf691e

Please sign in to comment.