diff --git a/test/civic/civic.variant.test.js b/test/civic/civic.variant.test.js index 4814157c..833a9c23 100644 --- a/test/civic/civic.variant.test.js +++ b/test/civic/civic.variant.test.js @@ -1,9 +1,129 @@ /* eslint-disable jest/no-disabled-tests */ -const { normalizeVariantRecord } = require('../../src/civic/variant'); +const { + normalizeFactorVariant, + normalizeFusionVariant, + normalizeGeneVariant, + normalizeVariant, + NotImplementedError, + uploadInferences, + uploadReferences, + uploadVariant, + uploadVariants, +} = require('../../src/civic/variant'); + + +/* + SYNCHRONOUS TESTS +*/ + +const civicVariantRecordsFactor = [ + { + feature: { featureInstance: { __typename: 'Factor', name: 'TMB' } }, + id: 123, + name: 'abc', + }, +]; +const civicVariantRecordsFusion = [ + { + feature: { + featureInstance: { + __typename: 'Fusion', + fivePrimeGene: { + entrezId: 673, + id: 5, + name: 'BRAF', + }, + }, + }, + id: 123, + }, + { + feature: { + featureInstance: { + __typename: 'Fusion', + threePrimeGene: { + entrezId: 238, + id: 1, + name: 'ALK', + }, + }, + }, + id: 123, + }, + { + feature: { + featureInstance: { + __typename: 'Fusion', + fivePrimeGene: { + entrezId: 673, + id: 5, + name: 'BRAF', + }, + threePrimeGene: { + entrezId: 238, + id: 1, + name: 'ALK', + }, + }, + }, + id: 123, + }, +]; +const civicVariantRecordsGene = [ + { + feature: { featureInstance: { __typename: 'Gene', entrezId: 672, name: 'BRCA1' } }, + name: 'Mutation', + }, +]; + + +describe('normalizeFactorVariant', () => { + test('testnormalizeFactorVariant', () => { + const normalizedVariants = normalizeFactorVariant(civicVariantRecordsFactor[0]); + expect(normalizedVariants.length).toEqual(1); + expect(normalizedVariants[0]).toEqual({ + reference1: { + class: 'Signature', + name: 'high mutation burden', + }, + type: 'high signature', + }); + }); +}); + +describe('normalizeFusionVariant', () => { + test('testnormalizeFusionVariantFivePrimeGeneOnly', () => { + const normalizedVariants = normalizeFusionVariant(civicVariantRecordsFusion[0]); + expect(normalizedVariants.length).toEqual(1); + expect(normalizedVariants[0]).toEqual({ + reference1: { name: 'braf', sourceId: '673' }, + type: 'fusion', + }); + }); + + test('testnormalizeFusionVariantThreePrimeGeneOnly', () => { + const normalizedVariants = normalizeFusionVariant(civicVariantRecordsFusion[1]); + expect(normalizedVariants.length).toEqual(1); + expect(normalizedVariants[0]).toEqual({ + reference1: { name: 'alk', sourceId: '238' }, + type: 'fusion', + }); + }); -describe('normalizeVariantRecord', () => { + test('testnormalizeFusionVariantBothGenes', () => { + const normalizedVariants = normalizeFusionVariant(civicVariantRecordsFusion[2]); + expect(normalizedVariants.length).toEqual(1); + expect(normalizedVariants[0]).toEqual({ + reference1: { name: 'braf', sourceId: '673' }, + reference2: { name: 'alk', sourceId: '238' }, + type: 'fusion', + }); + }); +}); + +describe('normalizeGeneVariant', () => { test('exon mutation', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'gene', name: 'EXON 12 MUTATION', @@ -19,7 +139,7 @@ describe('normalizeVariantRecord', () => { }); test('deleterious mutation', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'gene', name: 'DELETRIOUS MUTATION', @@ -31,7 +151,7 @@ describe('normalizeVariantRecord', () => { }); test('phosphorylation variant', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'gene', name: 'Y1234 phosphorylation', @@ -44,7 +164,7 @@ describe('normalizeVariantRecord', () => { }); test('single gene fusion with missense mutation', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'ALK FUSION G1202R', @@ -63,7 +183,7 @@ describe('normalizeVariantRecord', () => { }); test('multi-gene fusion with 2 resistance mutations (dash notation)', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'alk', name: 'EML4-ALK G1202R-L1198F', @@ -88,7 +208,7 @@ describe('normalizeVariantRecord', () => { }); test('multi-gene fusion', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'NRG1', name: 'CD74-NRG1', @@ -103,7 +223,7 @@ describe('normalizeVariantRecord', () => { }); test('fusion with multiple variants', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'NTRK1', name: 'LMNA-NTRK1 G595R AND G667C', @@ -128,7 +248,7 @@ describe('normalizeVariantRecord', () => { }); test('fusion with multiple variants (colon sep)', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'NTRK1', name: 'LMNA::NTRK1 G595R AND G667C', @@ -154,7 +274,7 @@ describe('normalizeVariantRecord', () => { test('corrects deprecated indel syntax', () => { // S111C (c.330CA>TT) - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'NTRK1', name: 'S111C (c.330CA>TT)', @@ -173,17 +293,8 @@ describe('normalizeVariantRecord', () => { ]); }); - test.skip('multiple variants with plus notation', () => { - // V600E+V600M - // E2014K + E2419K - }); - - test.skip('missense and amplification', () => { - // V600E AMPLIFICATION - }); - test('categorical variant', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'NTRK1', name: 'UNDEREXPRESSION', @@ -198,7 +309,7 @@ describe('normalizeVariantRecord', () => { test('protein truncation with cds notation', () => { // e46* (c.136g>t) - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'E46* (c.136G>T)', @@ -220,7 +331,7 @@ describe('normalizeVariantRecord', () => { }); test('categorical variant with spaces', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'NTRK1', name: 'DNA BINDING DOMAIN MUTATION', @@ -235,7 +346,7 @@ describe('normalizeVariantRecord', () => { test('regular missense mutation', () => { // R132H - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'NTRK1', name: 'R132H', @@ -251,7 +362,7 @@ describe('normalizeVariantRecord', () => { test('plural for single gene fusion', () => { // ALK FUSIONS - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'NRG1', name: 'NRG1 fusions', @@ -267,7 +378,7 @@ describe('normalizeVariantRecord', () => { test('fusion with exon positions', () => { // EML4-ALK E20;A20 // ALK FUSIONS - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'EML4-ALK E20;A20', @@ -285,7 +396,7 @@ describe('normalizeVariantRecord', () => { test('fusion with new exon notation', () => { // EWSR1-FLI1 e7-e6 // FLI1 Fusion - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'FLI1', name: 'EWSR1-FLI1 e7-e6', @@ -303,7 +414,7 @@ describe('normalizeVariantRecord', () => { test('fusion with reference2 input gene', () => { // EML4-ALK E20;A20 // ALK FUSIONS - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'EML4', name: 'EML4-ALK E20;A20', @@ -320,7 +431,7 @@ describe('normalizeVariantRecord', () => { test('abl fusion', () => { // BCR-ABL - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ABL1', name: 'BCR-ABL', @@ -336,7 +447,7 @@ describe('normalizeVariantRecord', () => { test('cds notation', () => { // BCR-ABL - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ABL1', name: 'c.123G>T', @@ -352,7 +463,7 @@ describe('normalizeVariantRecord', () => { test('exon range deletion', () => { // BCR-ABL - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ABL1', name: 'exon 2-3 deletion', @@ -367,7 +478,7 @@ describe('normalizeVariantRecord', () => { }); test('frameshift with cds', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 't133lfs*26 (c.397dela)', @@ -389,7 +500,7 @@ describe('normalizeVariantRecord', () => { }); test('protein indel with cds', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 't133lfs*26 (c.397dela)', @@ -412,7 +523,7 @@ describe('normalizeVariantRecord', () => { test('simple gene mutation', () => { // BCR-ABL - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ABL1', name: 'ABL1 mutations', @@ -426,7 +537,7 @@ describe('normalizeVariantRecord', () => { }); test('exon plural mutations', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ABL1', name: 'exon 3 mutations', @@ -441,7 +552,7 @@ describe('normalizeVariantRecord', () => { }); test('mutations', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ABL1', name: 'mutations', @@ -454,13 +565,9 @@ describe('normalizeVariantRecord', () => { ]); }); - test.skip('germline notation', () => { - // DPYD*2A HOMOZYGOSITY - }); - test('splice site mutation', () => { // F547 SPLICE SITE MUTATION - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'F547 SPLICE SITE MUTATION', @@ -477,7 +584,7 @@ describe('normalizeVariantRecord', () => { test('protein deletion with cds deletion sequence', () => { // r79_s80del (c.236_241delgcagtc) // r82_v84del (c.244_252del) - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'r79_s80del (c.236_241delgcagtc)', @@ -499,7 +606,7 @@ describe('normalizeVariantRecord', () => { }); test('protein deletion with cds deletion no sequence', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'r82_v84del (c.244_252del)', @@ -522,7 +629,7 @@ describe('normalizeVariantRecord', () => { test('protein dup with cds dup', () => { // p.s193_c196dupstsc (c.577_588dupagcaccagctgc) - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'p.s193_c196dupstsc (c.577_588dupagcaccagctgc)', @@ -545,7 +652,7 @@ describe('normalizeVariantRecord', () => { test('protein with cds notation', () => { // A122I (c.364_365GC>AT) - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'A122I (c.364_365GC>AT)', @@ -568,7 +675,7 @@ describe('normalizeVariantRecord', () => { test('OR-able position no alt seq', () => { // G12/G13 - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'G12/G13', @@ -582,13 +689,9 @@ describe('normalizeVariantRecord', () => { ]); }); - test.skip('catalogue variant', () => { - // RS3910384 - }); - test('semi-colon delimited variants', () => { // A50A (c.150C>G); Splicing alteration (c.463-1G>T) - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ALK', name: 'A50A (c.150C>G); Splicing alteration (c.463-1G>T)', @@ -618,6 +721,23 @@ describe('normalizeVariantRecord', () => { ]); }); + test.skip('multiple variants with plus notation', () => { + // V600E+V600M + // E2014K + E2419K + }); + + test.skip('missense and amplification', () => { + // V600E AMPLIFICATION + }); + + test.skip('germline notation', () => { + // DPYD*2A HOMOZYGOSITY + }); + + test.skip('catalogue variant', () => { + // RS3910384 + }); + test.skip('duplicate fusion', () => { // AGGF1-PDGFRB, AGGF1-PDGFRB C843G }); @@ -628,7 +748,7 @@ describe('normalizeVariantRecord', () => { describe('bad notation should return as vocabulary', () => { test('ERBB2 G776INSV_G/C', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ERBB2', name: 'ERBB2 G776INSV_G/C', @@ -640,7 +760,7 @@ describe('normalizeVariantRecord', () => { }); test('exon1 151nt del; Null (Partial deletion of Exon 1)', () => { - const variants = normalizeVariantRecord({ + const variants = normalizeGeneVariant({ entrezId: 1, entrezName: 'ERBB2', name: 'exon1 151nt del; Null (Partial deletion of Exon 1)', @@ -655,3 +775,172 @@ describe('normalizeVariantRecord', () => { }); }); }); + +describe('normalizeVariant', () => { + [ + civicVariantRecordsFactor[0], + civicVariantRecordsFusion[0], + civicVariantRecordsGene[0], + + ].forEach((record) => { + test(`testNormalizeVariantFeatureType${record.feature.featureInstance.__typename}`, () => { + expect(normalizeVariant(record).length).toBe(1); + }); + }); + + test('testNormalizeVariantFeatureTypeNotImplemented', () => { + expect(() => { + normalizeVariant( + { feature: { featureInstance: { __typename: 'Other' } } }, + ); + }).toThrow(NotImplementedError); + }); +}); + + +/* + ASYNCHRONOUS TESTS +*/ + +const mockConn = () => ({ + addRecord: jest.fn().mockResolvedValue({ '@rid': '#', reference1: '#' }), // used by Entrez loader + addSource: jest.fn().mockResolvedValue({ '@rid': '#', reference1: '#' }), // used by Entrez loader + addVariant: jest.fn().mockResolvedValue({ '@rid': '#123:45' }), + getUniqueRecordBy: jest.fn().mockResolvedValue({ '@rid': '#678:90', reference1: '#' }), + getVocabularyTerm: jest.fn().mockResolvedValue({ '@rid': '#' }), +}); +const conn = mockConn(); + + +describe.skip('uploadReferences', () => { + const normalizedVariants = [ + { }, + { reference1: { } }, + { reference1: { class: 'Signature', name: '' } }, + { reference1: { sourceId: '123' } }, + { reference1: { name: 'abc' } }, + { reference1: { sourceId: '123' }, reference2: { sourceId: '456' } }, + ]; + + test('testUploadReferencesNoReference1', async () => { + await expect( + uploadReferences(conn, normalizedVariants[0]), + ).rejects.toThrow('reference1 is mandatory on normalizedVariant'); + }); + + test('testUploadReferencesNoName', async () => { + await expect( + uploadReferences(conn, normalizedVariants[1]), + ).rejects.toThrow('name property is mandatory on normalizedVariant reference'); + }); + + test('testUploadReferencesSignature', async () => { + const [reference1, reference2] = await uploadReferences(conn, normalizedVariants[2]); + expect(reference1).toEqual({ '@rid': '#678:90', reference1: '#' }); + expect(reference2).toEqual(undefined); + }); + + test('testUploadReferencesWithSourceId', async () => { + const [reference1, reference2] = await uploadReferences(conn, normalizedVariants[3]); + expect(reference1).toEqual({ '@rid': '#678:90', reference1: '#' }); + expect(reference2).toEqual(undefined); + }); + + test('testUploadReferencesWithName', async () => { + const [reference1, reference2] = await uploadReferences(conn, normalizedVariants[4]); + expect(reference1).toEqual({ '@rid': '#678:90', reference1: '#' }); + expect(reference2).toEqual(undefined); + }); + + test('testUploadReferencesWithReference2', async () => { + const [reference1, reference2] = await uploadReferences(conn, normalizedVariants[5]); + expect(reference1).toEqual({ '@rid': '#678:90', reference1: '#' }); + expect(reference2).toEqual({ '@rid': '#678:90', reference1: '#' }); + }); +}); + +describe.skip('uploadInferences', () => { + const normalizedVariants = [ + { + infers: [ + { reference1: { name: '...' }, type: '...' }, + { reference1: { name: '...' }, type: '...' }, + ], + }, + { + inferredBy: [ + { reference1: { name: '...' }, type: '...' }, + { reference1: { name: '...' }, type: '...' }, + { reference1: { name: '...' }, type: '...' }, + ], + }, + ]; + + test('testUploadInferencesInfers', async () => { + const { links, variants } = await uploadInferences(conn, normalizedVariants[0], { '@rid': '#' }); + expect(links.infers.length).toEqual(2); + expect(variants.inferred.length).toEqual(2); + }); + + test('testUploadInferencesInferredBy', async () => { + const { links, variants } = await uploadInferences(conn, normalizedVariants[1], { '@rid': '#' }); + expect(links.inferredBy.length).toEqual(3); + expect(variants.inferring.length).toEqual(3); + }); +}); + +describe.skip('uploadVariant', () => { + const normalizedVariants = [ + { type: 'rs123' }, + { positional: true, reference1: { name: 'egfr', sourceId: 1956 }, variant: 'c.1del' }, + { reference1: { name: 'egfr', sourceId: 1956 }, type: 'mutation' }, + ]; + + test('testUploadVariantRSID', async () => { + const result = await uploadVariant(conn, normalizedVariants[0]); + expect(result).toEqual({ '@rid': '#678:90', reference1: '#' }); + }); + + test('testUploadVariantPositional', async () => { + const result = await uploadVariant(conn, normalizedVariants[1]); + expect(result).toEqual({ '@rid': '#123:45' }); + }); + + test('testUploadVariantCategory', async () => { + const result = await uploadVariant(conn, normalizedVariants[2]); + expect(result).toEqual({ '@rid': '#123:45' }); + }); +}); + +describe('uploadVariants', () => { + const normalizedVariants = [ + // Factor + { + reference1: { + class: 'Signature', + name: 'high mutation burden', + }, + type: 'high signature', + }, + // Fusion + { + reference1: { name: 'braf', sourceId: '673' }, + reference2: { name: 'alk', sourceId: '238' }, + type: 'fusion', + }, + // Gene + { + reference1: { name: 'braf', sourceId: '673' }, + type: 'mutation', + }, + ]; + + test('testuploadVariants', async () => { + const uploadedVariants = await uploadVariants(conn, normalizedVariants); + expect(uploadedVariants.length).toEqual(3); + + for (let i = 0; i < uploadedVariants.length; i++) { + expect(uploadedVariants[i]).toEqual({ '@rid': '#123:45' }); + } + }); +});