/************************************************************************* * * * This file is part of the 20n/act project. * * 20n/act enables DNA prediction for synthetic biology/bioengineering. * * Copyright (C) 2017 20n Labs, Inc. * * * * Please direct all queries to act@20n.com. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * *************************************************************************/ package com.act.biointerpretation.test.util; import act.server.MongoDB; import act.shared.Chemical; import act.shared.Organism; import act.shared.Reaction; import act.shared.Seq; import act.shared.helpers.MongoDBToJSON; import com.mongodb.DBObject; import org.json.JSONArray; import org.json.JSONObject; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; public class MockedMongoDB { public static final Answer CRASH_BY_DEFAULT = new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { throw new RuntimeException(String.format("Unexpected mock method called: %s", invocation.getMethod().getName())); } }; MongoDB mockMongoDB = null; Map<Long, Reaction> reactionMap = new HashMap<>(); Map<Long, Chemical> chemicalMap = new HashMap<>(); Map<Long, Seq> seqMap = new HashMap<>(); Map<Long, String> organismMap = new HashMap<>(); /* WARNING: floating point serializing/deserialization to JSON is not always exact; this function could cause issues for JSONObjects containing floating point numbers */ private static JSONObject deepCopy(JSONObject obj) { String objSerialized = obj.toString(); return new JSONObject(objSerialized); } private static Reaction copyReaction(Reaction r, Long newId) { Reaction newR = new Reaction(newId, r.getSubstrates(), r.getProducts(), r.getSubstrateCofactors(), r.getProductCofactors(), r.getCoenzymes(), r.getECNum(), r.getConversionDirection(), r.getPathwayStepDirection(), r.getReactionName(), r.getRxnDetailType()); for (JSONObject protein : r.getProteinData()) { JSONObject newProtein = new JSONObject(protein, JSONObject.getNames(protein)); newR.addProteinData(newProtein); } Long[] substrates = r.getSubstrates(); for (int i = 0; i < substrates.length; i++) { newR.setSubstrateCoefficient(substrates[i], r.getSubstrateCoefficient(substrates[i])); } Long[] products = r.getProducts(); for (int i = 0; i < products.length; i++) { newR.setProductCoefficient(products[i], r.getProductCoefficient(products[i])); } if (r.getMechanisticValidatorResult() != null) { newR.setMechanisticValidatorResult(deepCopy(r.getMechanisticValidatorResult())); } return newR; } private static Seq copySeq(Seq seq) { JSONObject oldMetadata = seq.getMetadata(); JSONObject metadata = deepCopy(oldMetadata); List<JSONObject> oldRefs = seq.getReferences(); List<JSONObject> references = new ArrayList<>(); for (JSONObject oldRef : oldRefs) { JSONObject copy = new JSONObject(oldRef, JSONObject.getNames(oldRef)); references.add(copy); } return new Seq(seq.getUUID(), seq.getEc(), seq.getOrgId(), seq.getOrgName(), seq.getSequence(), references, MongoDBToJSON.conv(metadata), seq.getSrcdb()); } public MockedMongoDB() { } public void installMocks(List<Reaction> testReactions, List<Seq> sequences, Map<Long, String> orgNames, Map<Long, String> chemIdToInchi) { installMocks(testReactions, Collections.EMPTY_LIST, sequences, orgNames, chemIdToInchi); } public void installMocks(List<Reaction> testReactions, List<Long> testChemIds, List<Seq> sequences, Map<Long, String> orgNames, Map<Long, String> chemIdToInchi) { if (orgNames != null) { this.organismMap.putAll(orgNames); } if (sequences != null) { for (Seq seq : sequences) { seqMap.put(Long.valueOf(seq.getUUID()), seq); } } // Mock the NoSQL API and its DB connections, throwing an exception if an unexpected method gets called. this.mockMongoDB = mock(MongoDB.class, CRASH_BY_DEFAULT); if (testReactions != null) { for (Reaction r : testReactions) { this.reactionMap.put(Long.valueOf(r.getUUID()), r); Long[] substrates = r.getSubstrates(); Long[] products = r.getProducts(); List<Long> allSubstratesProducts = new ArrayList<>(substrates.length + products.length); allSubstratesProducts.addAll(Arrays.asList(substrates)); allSubstratesProducts.addAll(Arrays.asList(products)); for (Long id : allSubstratesProducts) { if (!this.chemicalMap.containsKey(id)) { Chemical c = new Chemical(id); if (chemIdToInchi.containsKey(id)) { c.setInchi(chemIdToInchi.get(id)); } else { // Use /FAKE/BRENDA prefix to avoid computing InChI keys. c.setInchi(String.format("InChI=/FAKE/BRENDA/TEST/%d", id)); } this.chemicalMap.put(id, c); } } } } if (testChemIds != null) { for (Long id : testChemIds) { if (!this.chemicalMap.containsKey(id)) { Chemical c = new Chemical(id); if (chemIdToInchi.containsKey(id)) { c.setInchi(chemIdToInchi.get(id)); } else { // Use /FAKE/BRENDA prefix to avoid computing InChI keys. c.setInchi(String.format("InChI=/FAKE/BRENDA/TEST/%d", id)); } this.chemicalMap.put(id, c); } } } /* **************************************** * Method mocking */ doAnswer(new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws Throwable { return organismMap.get(invocation.getArgumentAt(0, Long.class)); } }).when(mockMongoDB).getOrganismNameFromId(any(Long.class)); doAnswer(new Answer<Seq>() { @Override public Seq answer(InvocationOnMock invocation) throws Throwable { return seqMap.get(invocation.getArgumentAt(0, Long.class)); } }).when(mockMongoDB).getSeqFromID(any(Long.class)); doAnswer(new Answer<Iterator<Seq>>() { @Override public Iterator<Seq> answer(InvocationOnMock invocation) throws Throwable { return seqMap.values().iterator(); } }).when(mockMongoDB).getSeqIterator(); doAnswer(new Answer<Iterator<Chemical>>() { @Override public Iterator<Chemical> answer(InvocationOnMock invocation) throws Throwable { List<Chemical> chemicals = new ArrayList<>(); for (Long id: (List<Long>) invocation.getArgumentAt(0, List.class)){ if (chemicalMap.get(id) == null) { return null; } chemicals.add(chemicalMap.get(id)); } return chemicals.iterator(); } }).when(mockMongoDB).getChemicalsbyIds(any(List.class), any(boolean.class)); doAnswer(new Answer<Iterator<Reaction>>() { @Override public Iterator<Reaction> answer(InvocationOnMock invocation) throws Throwable { List<Reaction> reactions = new ArrayList<>(); for (Long id: (List<Long>) invocation.getArgumentAt(0, List.class)){ if (reactionMap.get(id) == null) { return null; } reactions.add(reactionMap.get(id)); } return reactions.iterator(); } }).when(mockMongoDB).getReactionsIteratorById(any(List.class), any(boolean.class)); doAnswer(new Answer<Long>() { @Override public Long answer(InvocationOnMock invocation) throws Throwable { String targetOrganism = invocation.getArgumentAt(0, String.class); for (Map.Entry<Long, String> entry : organismMap.entrySet()) { if (entry.getValue().equals(targetOrganism)) { return entry.getKey(); } } return -1L; } }).when(mockMongoDB).getOrganismId(any(String.class)); doAnswer(new Answer<Long> () { @Override public Long answer(InvocationOnMock invocation) throws Throwable { Long id = (long) organismMap.size(); String name = invocation.getArgumentAt(0, String.class); organismMap.put(id, name); return id; } }).when(mockMongoDB).submitToActOrganismNameDB(any(String.class)); doAnswer(new Answer<List<Seq>> () { @Override public List<Seq> answer(InvocationOnMock invocation) throws Throwable { String seq = invocation.getArgumentAt(0, String.class); String ec = invocation.getArgumentAt(1, String.class); String organism = invocation.getArgumentAt(2, String.class); List<Seq> matchedSeqs = new ArrayList<>(); for (Map.Entry<Long, Seq> entry : seqMap.entrySet()) { Seq sequence = entry.getValue(); if (sequence.getEc() != null && sequence.getEc().equals(ec) && sequence.getSequence().equals(seq) && sequence.getOrgName().equals(organism)) { matchedSeqs.add(copySeq(sequence)); } } return matchedSeqs; } }).when(mockMongoDB).getSeqFromSeqEcOrg(any(String.class), any(String.class), any(String.class)); doAnswer(new Answer<List<Seq>> () { @Override public List<Seq> answer(InvocationOnMock invocation) throws Throwable { String accession = invocation.getArgumentAt(0, String.class); List<Seq> matchedSeqs = new ArrayList<>(); for (Map.Entry<Long, Seq> entry : seqMap.entrySet()) { Seq sequence = entry.getValue(); JSONObject metadata = sequence.getMetadata(); if (!metadata.has("accession") || !metadata.getJSONObject("accession").has(Seq.AccType.genbank_protein.toString())) { continue; } JSONArray accessionArray = metadata.getJSONObject("accession").getJSONArray(Seq.AccType.genbank_protein.toString()); for (int i = 0; i < accessionArray.length(); i++) { if (accessionArray.getString(i).equals(accession)) { matchedSeqs.add(copySeq(sequence)); break; } } } return matchedSeqs; } }).when(mockMongoDB).getSeqFromGenbankProtAccession(any(String.class)); doAnswer(new Answer<List<Seq>> () { @Override public List<Seq> answer(InvocationOnMock invocation) throws Throwable { String accession = invocation.getArgumentAt(0, String.class); String seq = invocation.getArgumentAt(1, String.class); List<Seq> matchedSeqs = new ArrayList<>(); for (Map.Entry<Long, Seq> entry : seqMap.entrySet()) { Seq sequence = entry.getValue(); JSONObject metadata = sequence.getMetadata(); String databaseSequence = sequence.getSequence(); if (!seq.equals(databaseSequence)) { continue; } if (!metadata.has("accession") || !metadata.getJSONObject("accession").has(Seq.AccType.genbank_nucleotide.toString())) { continue; } JSONArray accessionArray = metadata.getJSONObject("accession").getJSONArray(Seq.AccType.genbank_nucleotide.toString()); for (int i = 0; i < accessionArray.length(); i++) { if (accessionArray.getString(i).equals(accession)) { matchedSeqs.add(copySeq(sequence)); break; } } } return matchedSeqs; } }).when(mockMongoDB).getSeqFromGenbankNucAccessionSeq(any(String.class), any(String.class)); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Seq seq = invocation.getArgumentAt(0, Seq.class); if (seqMap.containsKey((long) seq.getUUID())) { seqMap.get((long) seq.getUUID()).setMetadata(seq.getMetadata()); } return null; } }).when(mockMongoDB).updateMetadata(any(Seq.class)); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Seq seq = invocation.getArgumentAt(0, Seq.class); if (seqMap.containsKey((long) seq.getUUID())) { seqMap.get((long) seq.getUUID()).setReferences(seq.getReferences()); } return null; } }).when(mockMongoDB).updateReferences(any(Seq.class)); // See http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#do_family_methods_stubs doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Reaction toBeUpdated = invocation.getArgumentAt(0, Reaction.class); int id = invocation.getArgumentAt(1, Integer.class); Reaction newR = copyReaction(toBeUpdated, Long.valueOf(id)); reactionMap.remove(Long.valueOf(id)); reactionMap.put(Long.valueOf(id), newR); return null; } }).when(mockMongoDB).updateActReaction(any(Reaction.class), anyInt()); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Long id = organismMap.size() + 1L; organismMap.put(id, invocation.getArgumentAt(0, Organism.class).getName()); return null; } }).when(mockMongoDB).submitToActOrganismNameDB(any(Organism.class)); doAnswer(new Answer<Integer>() { @Override public Integer answer(InvocationOnMock invocation) throws Throwable { Long id = seqMap.size() + 1L; Seq.AccDB src = invocation.getArgumentAt(0, Seq.AccDB.class); String ec = invocation.getArgumentAt(1, String.class); String org = invocation.getArgumentAt(2, String.class); Long org_id = invocation.getArgumentAt(3, Long.class); String seq = invocation.getArgumentAt(4, String.class); List<JSONObject> pmids = invocation.getArgumentAt(5, List.class); Set<Long> rxns = invocation.getArgumentAt(6, Set.class); DBObject meta = invocation.getArgumentAt(7, DBObject.class); seqMap.put(id, Seq.rawInit(id, ec, org_id, org, seq, pmids, meta, src, rxns)); return id.intValue(); } }).when(mockMongoDB).submitToActSeqDB( any(Seq.AccDB.class), any(String.class), any(String.class), any(Long.class), any(String.class), any(List.class), any(Set.class), any(DBObject.class) ); } public MongoDB getMockMongoDB() { return mockMongoDB; } public Map<Long, Reaction> getReactionMap() { return reactionMap; } public Map<Long, Chemical> getChemicalMap() { return chemicalMap; } public Map<Long, String> getOrganismMap() { return organismMap; } public Map<Long, Seq> getSeqMap() { return seqMap; } private Set<String> chemMapToInchiSet(Long[] ids, Map<Long, Chemical> chemMap) { Set<String> inchis = new HashSet<>(); for (Long id : ids) { Chemical c = chemMap.get(id); // Let NPEs happen here if bad ids are passed. inchis.add(c.getInChI()); } return inchis; } public Set<String> readDBChemicalIdsToInchis(Long[] ids) { return this.chemMapToInchiSet(ids, this.chemicalMap); } }