content: /*************************************************************************
  *                                                                        *
  *  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.mechanisminspection;
  
  import act.server.NoSQLAPI;
  import act.shared.Reaction;
  import com.act.biointerpretation.desalting.ReactionDesalter;
  import com.act.biointerpretation.test.util.MockedNoSQLAPI;
  import com.act.biointerpretation.test.util.TestUtils;
  import junit.framework.Assert;
  import org.json.JSONObject;
  import org.junit.Before;
  import org.junit.Test;
  import org.mockito.MockitoAnnotations;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertTrue;
  
  public class MechanisticValidatorTest {
  
    TestUtils utilsObject;
  
    @Before
    public void setUp() throws Exception {
      // In case we ever use Mockito annotations, don't forget to initialize them.
      MockitoAnnotations.initMocks(ReactionDesalter.class);
      utilsObject = new TestUtils();
    }
  
    @Test
    public void testAllRosHaveSubstrateAndProductCounts() throws Exception {
      ErosCorpus erosCorpus = new ErosCorpus();
      erosCorpus.loadValidationCorpus();
      for (Ero ro : erosCorpus.getRos()) {
        Assert.assertNotNull(ro.getProduct_count());
        Assert.assertNotNull(ro.getSubstrate_count());
      }
    }
  
    @Test
    public void testMechanisticValidatorIsMatchingTheCorrectROsToReaction() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
      Map<Long, String> idToInchi = new HashMap<>();
  
      // The first inchi is a cofactor while the second is not.
      idToInchi.put(1L, "InChI=1S/p+1");
      idToInchi.put(2L, "InChI=1S/C5H10O/c1-3-4-5(2)6/h3-4H2,1-2H3");
      idToInchi.put(3L, "InChI=1S/C5H12O/c1-3-4-5(2)6/h5-6H,3-4H2,1-2H3/t5-/m1/s1");
  
      JSONObject expectedResult = new JSONObject();
  
      // The first RO is an alcohol oxidation to aldehyde. The score is 4 since the curated ERO list sets the category
      // to perfect.
      expectedResult.put("3", "4");
  
      // This RO is for primary alcohol to aldehyde. Similar but more specific RO than the above.
      expectedResult.put("337", "4");
  
      // This RO is for secondary alcohol to aldehyde. Similar again to the above.
      expectedResult.put("340", "4");
  
      Long[] products = {1L, 2L};
      Long[] substrates = {3L};
  
      Integer[] substrateCoefficients = {1};
      Integer[] productCoefficients = {1, 1};
  
      Reaction testReaction =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      MechanisticValidator mechanisticValidator = new MechanisticValidator(mockNoSQLAPI);
      mechanisticValidator.init();
      mechanisticValidator.run();
  
      assertEquals("One reaction should be written to the DB", 1, mockAPI.getWrittenReactions().size());
      assertEquals("The mechanistic validator result should be equal to the expected result",
          expectedResult.toString(), mockAPI.getWrittenReactions().get(0).getMechanisticValidatorResult().toString());
    }
  
    @Test
    public void testMechanisticValidatorDoesNotAddAnyROScoresWhenNoMatchesHappen() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
      Map<Long, String> idToInchi = new HashMap<>();
  
      // The first inchi is a cofactor while the second is not.
      idToInchi.put(1L, "InChI=1S/p+1");
      idToInchi.put(2L, "InChI=1S/H2O/h1H2");
  
      Long[] products = {2L};
      Long[] substrates = {1L};
  
      Integer[] substrateCoefficients = {1};
      Integer[] productCoefficients = {1};
  
      Reaction testReaction =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      MechanisticValidator mechanisticValidator = new MechanisticValidator(mockNoSQLAPI);
      mechanisticValidator.init();
      mechanisticValidator.run();
  
      assertEquals("One reaction should be written to the DB", 1, mockAPI.getWrittenReactions().size());
      assertEquals("The mechanistic validator result should be null since no ROs should react with the reaction",
          null, mockAPI.getWrittenReactions().get(0).getMechanisticValidatorResult());
    }
  
    @Test
    public void testMechanisticValidatorIsMatchingTheCorrectROsToReactionThatAreNotPerfect() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
      Map<Long, String> idToInchi = new HashMap<>();
  
      // The first inchi is a cofactor while the second is not.
      idToInchi.put(1L, "InChI=1S/p+1");
      idToInchi.put(2L, "InChI=1S/C25H40N7O19P3S/c1-25(2,20(38)23(39)28-6-5-14(33)27-7-8-55-16(36)4-3-15(34)35)10-48-54(45,46)51-53(43,44)47-9-13-19(50-52(40,41)42)18(37)24(49-13)32-12-31-17-21(26)29-11-30-22(17)32/h11-13,18-20,24,37-38H,3-10H2,1-2H3,(H,27,33)(H,28,39)(H,34,35)(H,43,44)(H,45,46)(H2,26,29,30)(H2,40,41,42)/t13-,18-,19-,20+,24-/m1/s1");
      idToInchi.put(3L, "InChI=1S/C4H6O3/c5-3-1-2-4(6)7/h3H,1-2H2,(H,6,7)");
  
      Long[] products = {1L, 2L};
      Long[] substrates = {3L};
  
      JSONObject expectedResult = new JSONObject();
  
      // This RO has no name, but it currently in the "validated" category. Hence, the score should be 3.
      expectedResult.put("284", "3");
  
      Integer[] substrateCoefficients = {1};
      Integer[] productCoefficients = {1, 1};
  
      Reaction testReaction =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      MechanisticValidator mechanisticValidator = new MechanisticValidator(mockNoSQLAPI);
      mechanisticValidator.init();
      mechanisticValidator.run();
  
      assertEquals("One reaction should be written to the DB", 1, mockAPI.getWrittenReactions().size());
      assertEquals("The mechanistic validator result should be equal to the expected result",
          expectedResult.toString(), mockAPI.getWrittenReactions().get(0).getMechanisticValidatorResult().toString());
    }
  
    @Test
    public void testMechanisticValidatorIsCorrectlyUsingCoefficients() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
      Map<Long, String> idToInchi = new HashMap<>();
  
      idToInchi.put(1L, "InChI=1S/CH4O/c1-2/h2H,1H3");
      idToInchi.put(2L, "InChI=1S/CH5O4P/c1-5-6(2,3)4/h1H3,(H2,2,3,4)");
  
      Long[] products = {1L, 2L};
      Long[] substrates = {1L, 2L};
  
      // These coefficients meet the expectations of the RO we'll look for later.
      Integer[] substrateCoefficients = {2, 1};
      Integer[] productCoefficients = {1, 2};
  
      JSONObject expectedResult = new JSONObject();
  
      // This RO acts on two identical substrates.
      expectedResult.put("165", "3");
  
      Reaction testReaction =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      MechanisticValidator mechanisticValidator = new MechanisticValidator(mockNoSQLAPI);
      mechanisticValidator.init();
      mechanisticValidator.run();
  
      assertEquals("One reaction should be written to the DB", 1, mockAPI.getWrittenReactions().size());
      assertEquals("The mechanistic validator result should be equal to the expected result",
          expectedResult.toString(), mockAPI.getWrittenReactions().get(0).getMechanisticValidatorResult().toString());
    }
  
  
    @Test
    public void testMechanisticValidatorMatchesNonPrimaryReactorPrediction() throws Exception {
      // Arrange
      List<Reaction> testReactions = new ArrayList<>();
      Map<Long, String> idToInchi = new HashMap<>();
  
      Long substrateId = 1L;
      Long productId = 2L;
  
      String substrateInchi =
          "InChI=1S/C15H10O7/c16-7-4-10(19)12-11(5-7)22-15(14(21)13(12)20)6-1-2-8(17)9(18)3-6/h1-5,16-19,21H";
      String productInchi =
          "InChI=1S/C16H12O7/c1-22-11-3-2-7(4-9(11)18)16-15(21)14(20)13-10(19)5-8(17)6-12(13)23-16/h2-6,17-19,21H,1H3";
  
      idToInchi.put(substrateId, substrateInchi);
      idToInchi.put(productId, productInchi);
  
      Long[] substrates = {substrateId};
      Long[] products = {productId};
  
      Integer[] substrateCoefficients = {1};
      Integer[] productCoefficients = {1};
  
      String expectedRo = "358";
      String expectedScore = "4";
  
      Reaction testReaction =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      // Act
      MechanisticValidator mechanisticValidator = new MechanisticValidator(mockNoSQLAPI);
      mechanisticValidator.init();
      mechanisticValidator.run();
  
      // Assert
      assertEquals("One reaction should be written to the DB", 1, mockAPI.getWrittenReactions().size());
      assertTrue("The mechanistic validator result should contain the expected RO as a key.",
          mockAPI.getWrittenReactions().get(0).getMechanisticValidatorResult().has(expectedRo));
      assertEquals("The mechanistic validator result should contain the correct score for that RO.",
          expectedScore, mockAPI.getWrittenReactions().get(0).getMechanisticValidatorResult().get(expectedRo));
    }
  }
  