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.desalting;
  
  import act.server.NoSQLAPI;
  import act.shared.Reaction;
  import com.act.biointerpretation.test.util.MockedNoSQLAPI;
  import com.act.biointerpretation.test.util.TestUtils;
  import org.junit.After;
  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;
  
  public class ReactionDesalterTest {
  
    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();
    }
  
    @After
    public void tearDown() throws Exception {}
  
    @Test
    public void testFakeReactionShouldBeWrittenToWriteDBInTheDesaltingProcess() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
  
      Long[] substrates = {1L, 2L, 3L};
      Long[] products = {4L, 5L, 6L};
      Integer[] substrateCoefficients = {1, 2, 3};
      Integer[] productCoefficients = {2, 3, 1};
  
      Reaction testReaction1 =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, true);
      Reaction testReaction2 =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction1);
      testReactions.add(testReaction2);
  
      for (int i = 0; i < substrates.length; i++) {
        assertEquals(String.format("Input reaction substrate %d has correct coefficient set", substrates[i]),
            substrateCoefficients[i], testReactions.get(0).getSubstrateCoefficient(substrates[i]));
      }
      for (int i = 0; i < products.length; i++) {
        assertEquals(String.format("Input reaction product %d has correct coefficient set", products[i]),
            productCoefficients[i], testReactions.get(0).getProductCoefficient(products[i]));
      }
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, new HashMap<>());
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      ReactionDesalter testReactionDesalter = new ReactionDesalter(mockNoSQLAPI);
      testReactionDesalter.init();
      testReactionDesalter.run();
  
      assertEquals("Two similar fake reactions should be merged into one in the write DB", 2,
          mockAPI.getWrittenReactions().size());
  
      for (int i = 0; i < mockAPI.getWrittenReactions().size(); i++) {
        assertEquals("The reaction written to the DB should exactly match the input reaction",
            testReactions.get(i), mockAPI.getWrittenReactions().get(i));
      }
    }
  
    @Test
    public void testSubstratesThatMapToTheSameDesaltedSubstrateShouldBeWrittenToTheDBAsTwoEntries() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
  
      Long[] products = {4L};
  
      Map<Long, String> idToInchi = new HashMap<>();
  
      // The two inchis below, once desalted, map to InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)
      idToInchi.put(1L, "InChI=1S/CH2O2.K/c2-1-3;/h1H,(H,2,3);/q;+1/p-1");
      idToInchi.put(2L, "InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)/p-1");
  
      Integer[] substrateCoefficients = {3};
      Integer[] productCoefficients = {5};
  
      Long[] substrates1 = {1L};
      Long[] substrates2 = {2L};
  
      Reaction testReaction1 =
          utilsObject.makeTestReaction(substrates1, products, substrateCoefficients, productCoefficients, true);
  
      Reaction testReaction2 =
          utilsObject.makeTestReaction(substrates2, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction1);
      testReactions.add(testReaction2);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      ReactionDesalter testReactionDesalter = new ReactionDesalter(mockNoSQLAPI);
      testReactionDesalter.init();
      testReactionDesalter.run();
  
      assertEquals("Similar pre-desalted substrates should be written as two entries", 2,
          mockAPI.getWrittenReactions().size());
  
      for (int i = 0; i < mockAPI.getWrittenReactions().size(); i++) {
        Reaction rxn = mockAPI.getWrittenReactions().get(i);
        for (Long substrateId : rxn.getSubstrateIdsOfSubstrateCoefficients()) {
          assertEquals("Make sure the substrate coefficients are preserved during the migration",
              substrateCoefficients[0], rxn.getSubstrateCoefficient(substrateId));
        }
  
        for (Long productId : rxn.getProductIdsOfProductCoefficients()) {
          assertEquals("Make sure the product coefficients are preserved during the migration",
              productCoefficients[0], rxn.getProductCoefficient(productId));
        }
      }
  
      for (int i = 0; i < mockAPI.getWrittenReactions().size(); i++) {
        Long rnxSubstrateId = mockAPI.getWrittenReactions().get(i).getSubstrates()[0];
        assertEquals("The reaction written to the write DB should have the desalted inchi name",
            "InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)", mockAPI.getWrittenChemicals().get(rnxSubstrateId).getInChI());
      }
    }
  
    @Test
    public void testSubstratesThatMapsToTheSameDesaltedSubstrateButWithDifferentSubtrateCoefficientsShouldBeWrittenAsTwoEntries()
        throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
  
      Long[] products = {4L};
  
      Map<Long, String> idToInchi = new HashMap<>();
      // The inchi below, once desalted, maps to InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)
      idToInchi.put(1L, "InChI=1S/CH2O2.K/c2-1-3;/h1H,(H,2,3);/q;+1/p-1");
  
      Long[] substrates = {1L};
  
      Integer[] substrateCoefficients = {3};
      Integer[] substrateCoefficients1 = {5};
  
      Integer[] productCoefficients = {2};
  
      Reaction testReaction1 =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients, productCoefficients, true);
  
      Reaction testReaction2 =
          utilsObject.makeTestReaction(substrates, products, substrateCoefficients1, productCoefficients, true);
  
      testReactions.add(testReaction1);
      testReactions.add(testReaction2);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      ReactionDesalter testReactionDesalter = new ReactionDesalter(mockNoSQLAPI);
      testReactionDesalter.init();
      testReactionDesalter.run();
  
      assertEquals("Even if the same pre-desalted chemical mapped to the same desalter chemical," +
          "there should be 2 entries in the write DB when the substrate coefficients are not the same",
          2, mockAPI.getWrittenReactions().size());
  
      // Hacky way of mapping reactions to their respective subs/prods coefficients.  TODO: do this better!
      Integer[][] rxnSubstrateCoefficients = new Integer[][] {substrateCoefficients, substrateCoefficients1};
      Integer[][] rxnProductCoefficients = new Integer[][] {productCoefficients, productCoefficients};
  
      for (int i = 0; i < mockAPI.getWrittenReactions().size(); i++) {
        Reaction rxn = mockAPI.getWrittenReactions().get(i);
        for (Long substrateId : rxn.getSubstrateIdsOfSubstrateCoefficients()) {
          assertEquals("Make sure the substrate coefficients are preserved during the migration",
              rxnSubstrateCoefficients[i][0], rxn.getSubstrateCoefficient(substrateId));
        }
  
        for (Long productId : rxn.getProductIdsOfProductCoefficients()) {
          assertEquals("Make sure the product coefficients are preserved during the migration",
              rxnProductCoefficients[i][0], rxn.getProductCoefficient(productId));
        }
      }
  
      Long rnxSubstrateId1 = mockAPI.getWrittenReactions().get(0).getSubstrates()[0];
      Long rnxSubstrateId2 = mockAPI.getWrittenReactions().get(1).getSubstrates()[0];
      assertEquals("The reaction written to the write DB should have the desalted inchi",
          "InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)", mockAPI.getWrittenChemicals().get(rnxSubstrateId1).getInChI());
      assertEquals("The reaction written to the write DB should have the desalted inchi",
          "InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)", mockAPI.getWrittenChemicals().get(rnxSubstrateId2).getInChI());
    }
  
    @Test
    public void testDuplicateDesaltedChemicalsInSubstratesAndProductsMapToTheCorrectSubstratesAndProductsAndCoefficientsAreNotNull() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
  
      Map<Long, String> idToInchi = new HashMap<Long, String>() {{
        // The two inchis below, once desalted, map to InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)
        put(1L, "InChI=1S/C36H36N4O2.Fe/c1-9-25-21(5)29-15-30-23(7)27(13-11-19(3)41)35(39-30)18-36-28(14-12-20(4)42)24(8)32(40-36)17-34-26(10-2)22(6)31(38-34)16-33(25)37-29;/h9-10,15-18H,1-2,11-14H2,3-8H3;/q-2;+7/b29-15-,30-15-,31-16-,32-17-,33-16-,34-17-,35-18-,36-18-;");
        put(2L, "InChI=1S/6CN.Fe.K.H/c6*1-2;;;/q;;;;;;;+1;");
        put(3L, "InChI=1S/p+1");
  
        put(4L, "InChI=1S/H2O/h1H2");
        put(5L, "InChI=1S/C36H36N4O2.Fe/c1-9-25-21(5)29-15-30-23(7)27(13-11-19(3)41)35(39-30)18-36-28(14-12-20(4)42)24(8)32(40-36)17-34-26(10-2)22(6)31(38-34)16-33(25)37-29;/h9-10,15-18H,1-2,11-14H2,3-8H3;/q-2;+6/b29-15-,30-15-,31-16-,32-17-,33-16-,34-17-,35-18-,36-18-;");
        put(6L, "InChI=1S/6CN.Fe.3K/c6*1-2;;;;/q;;;;;;-3;3*+1");
      }};
  
      Map<Long, String> idToDesaltedInchi =  new HashMap<Long, String>() {{
        put(1L, "InChI=1S/C36H36N4O2/c1-9-25-21(5)29-15-30-23(7)27(13-11-19(3)41)35(39-30)18-36-28(14-12-20(4)42)24(8)32(40-36)17-34-26(10-2)22(6)31(38-34)16-33(25)37-29/h9-10,15-18H,1-2,11-14H2,3-8H3/q-2/b29-15-,30-15?,31-16?,32-17-,33-16-,34-17?,35-18-,36-18?");
        put(2L, "InChI=1S/CN/c1-2");
        put(3L, "InChI=1S/p+1");
  
        put(4L, "InChI=1S/H2O/h1H2");
        put(5L, "InChI=1S/C36H36N4O2/c1-9-25-21(5)29-15-30-23(7)27(13-11-19(3)41)35(39-30)18-36-28(14-12-20(4)42)24(8)32(40-36)17-34-26(10-2)22(6)31(38-34)16-33(25)37-29/h9-10,15-18H,1-2,11-14H2,3-8H3/q-2/b29-15-,30-15?,31-16?,32-17-,33-16-,34-17?,35-18-,36-18?");
        put(6L, "InChI=1S/CN/c1-2");
      }};
  
      Integer[] substrateCoefficients = {2, 17, 19};
      Integer[] productCoefficients = {1000, 3, 91};
  
      Long[] substrates = {4L, 5L, 6L};
      Long[] products = {1L, 2L, 3L};
  
      Integer[] expectedSubstrateCoefficients = {2, 17, 19 * 6};
      Integer[] expectedProductCoefficients = {1000, 3 * 6, 91};
  
      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();
  
      ReactionDesalter testReactionDesalter = new ReactionDesalter(mockNoSQLAPI);
      testReactionDesalter.init();
      testReactionDesalter.run();
  
      // substrates comparison
      Reaction rxn = mockAPI.getWrittenReactions().get(0);
      assertEquals("Substrate 1 has correct coefficient", expectedSubstrateCoefficients[0], rxn.getSubstrateCoefficient(rxn.getSubstrates()[0]));
      assertEquals("Substrate 1 has correct inchi", idToDesaltedInchi.get(4L), mockAPI.getWrittenChemicals().get(rxn.getSubstrates()[0]).getInChI());
  
      assertEquals("Substrate 2 has correct coefficient", expectedSubstrateCoefficients[1], rxn.getSubstrateCoefficient(rxn.getSubstrates()[1]));
      assertEquals("Substrate 3 has correct inchi", idToDesaltedInchi.get(5L), mockAPI.getWrittenChemicals().get(rxn.getSubstrates()[1]).getInChI());
  
      assertEquals("Substrate 3 has correct coefficient", expectedSubstrateCoefficients[2], rxn.getSubstrateCoefficient(rxn.getSubstrates()[2]));
      assertEquals("Substrate 3 has correct inchi", idToDesaltedInchi.get(6L), mockAPI.getWrittenChemicals().get(rxn.getSubstrates()[2]).getInChI());
  
      // products comparison
      assertEquals("Product 1 has correct coefficient", expectedProductCoefficients[0], rxn.getProductCoefficient(rxn.getProducts()[0]).intValue(), 1);
      assertEquals("Product 1 has correct inchi", idToDesaltedInchi.get(1L), mockAPI.getWrittenChemicals().get(rxn.getProducts()[0]).getInChI());
  
      assertEquals("Product 2 has correct coefficient", expectedProductCoefficients[1], rxn.getProductCoefficient(rxn.getProducts()[1]).intValue(), 3);
      assertEquals("Product 1 has correct inchi", idToDesaltedInchi.get(2L), mockAPI.getWrittenChemicals().get(rxn.getProducts()[1]).getInChI());
  
      assertEquals("Product 3 has correct coefficient", expectedProductCoefficients[2], rxn.getProductCoefficient(rxn.getProducts()[2]).intValue(), 1);
      assertEquals("Product 1 has correct inchi", idToDesaltedInchi.get(3L), mockAPI.getWrittenChemicals().get(rxn.getProducts()[2]).getInChI());
  
    }
  
    @Test
    public void testSubstratesThatMapsToTheSameDesaltedSubstrateHaveTheirCoefficientsMerged() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
  
      Long[] products = {4L};
  
      Map<Long, String> idToInchi = new HashMap<>();
  
      // The two inchis below, once desalted, map to InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)
      idToInchi.put(1L, "InChI=1S/CH2O2.K/c2-1-3;/h1H,(H,2,3);/q;+1/p-1");
      idToInchi.put(2L, "InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)/p-1");
  
      Integer[] substrateCoefficients = {3, 5};
      Integer[] productCoefficients = {5};
  
      Long[] substrates1 = {1L, 2L};
  
      Reaction testReaction1 =
          utilsObject.makeTestReaction(substrates1, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction1);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      ReactionDesalter testReactionDesalter = new ReactionDesalter(mockNoSQLAPI);
      testReactionDesalter.init();
      testReactionDesalter.run();
  
      assertEquals("One desalted reaction should be written", 1,
          mockAPI.getWrittenReactions().size());
  
      Reaction rxn = mockAPI.getWrittenReactions().get(0);
      for (Long substrateId : rxn.getSubstrateIdsOfSubstrateCoefficients()) {
        assertEquals("Desalted substrate coefficients should have been added together",
            Integer.valueOf(substrateCoefficients[0] + substrateCoefficients[1]),
            rxn.getSubstrateCoefficient(substrateId));
      }
  
      for (Long productId : rxn.getProductIdsOfProductCoefficients()) {
        assertEquals("Product coefficients should remain unchanged",
            productCoefficients[0], rxn.getProductCoefficient(productId));
      }
  
      Long rnxSubstrateId = rxn.getSubstrates()[0];
      assertEquals("The reaction written to the write DB should have the desalted inchi name",
          "InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)", mockAPI.getWrittenChemicals().get(rnxSubstrateId).getInChI());
    }
  
    @Test
    public void testSubstratesWithNullCoefficientsKeepThemEvenThroughAMerge() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
  
      Long[] products = {4L};
  
      Map<Long, String> idToInchi = new HashMap<>();
  
      // The two inchis below, once desalted, map to InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)
      idToInchi.put(1L, "InChI=1S/CH2O2.K/c2-1-3;/h1H,(H,2,3);/q;+1/p-1");
      idToInchi.put(2L, "InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)/p-1");
  
      Integer[] substrateCoefficients = {3, null}; // null + integer -> null when substrates/products merge
      Integer[] productCoefficients = {5};
  
      Long[] substrates1 = {1L, 2L};
  
      Reaction testReaction1 =
          utilsObject.makeTestReaction(substrates1, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction1);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      ReactionDesalter testReactionDesalter = new ReactionDesalter(mockNoSQLAPI);
      testReactionDesalter.init();
      testReactionDesalter.run();
  
      assertEquals("One desalted reaction should be written", 1,
          mockAPI.getWrittenReactions().size());
  
      Reaction rxn = mockAPI.getWrittenReactions().get(0);
      for (Long substrateId : rxn.getSubstrateIdsOfSubstrateCoefficients()) {
        assertEquals("Desalted substrate coefficients should be null",
            null, rxn.getSubstrateCoefficient(substrateId));
      }
  
      for (Long productId : rxn.getProductIdsOfProductCoefficients()) {
        assertEquals("Product coefficients should remain unchanged",
            productCoefficients[0], rxn.getProductCoefficient(productId));
      }
  
      Long rnxSubstrateId = rxn.getSubstrates()[0];
      assertEquals("The reaction written to the write DB should have the desalted inchi name",
          "InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)", mockAPI.getWrittenChemicals().get(rnxSubstrateId).getInChI());
    }
  
    @Test
    public void testCoefficientsAreMultipliedByTheNumberOfDesaltedFragments() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
  
      Map<Long, String> idToInchi = new HashMap<>();
  
      // The two inchis below, once desalted, map to InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)
      idToInchi.put(1L, "InChI=1S/2C12H8N2.2ClH.Ru/c2*1-3-9-5-6-10-4-2-8-14-12(10)11(9)13-7-1;;;/h2*1-8H;2*1H;/q2*-2;;;+9");
      idToInchi.put(2L, "InChI=1S/3C14H12N2.F6P.Ru/c3*1-9-3-4-12-11(7-9)8-16-14-13(12)10(2)5-6-15-14;1-7(2,3,4,5)6;/h3*3-8H,1-2H3;;/q;;;-1;+3");
  
      String expectedDesaltingInchi1 = "InChI=1S/C12H8N2/c1-3-9-5-6-10-4-2-8-14-12(10)11(9)13-7-1/h1-8H/q-2";
      String expectedDesaltingInchi2 = "InChI=1S/C14H12N2/c1-9-3-4-12-11(7-9)8-16-14-13(12)10(2)5-6-15-14/h3-8H,1-2H3";
  
      Long[] substrates1 = {1L};
      Long[] products = {2L};
  
      Integer[] substrateCoefficients = {3};
      Integer[] productCoefficients = {5};
  
      Integer[] expectedSubstrateCoefficients = {3 * 2};
      Integer[] expectedProductCoefficients = {5 * 3};
  
      Reaction testReaction1 =
          utilsObject.makeTestReaction(substrates1, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction1);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      ReactionDesalter testReactionDesalter = new ReactionDesalter(mockNoSQLAPI);
      testReactionDesalter.init();
      testReactionDesalter.run();
  
      assertEquals("One desalted reaction should be written", 1,
          mockAPI.getWrittenReactions().size());
  
      Reaction rxn = mockAPI.getWrittenReactions().get(0);
  
      Long[] newSubstrates = rxn.getSubstrates();
      assertEquals("One substrate remains after desalting", 1, newSubstrates.length);
      assertEquals("Single substrate has expected inchi", expectedDesaltingInchi1,
          mockAPI.getWrittenChemicals().get(newSubstrates[0]).getInChI());
      assertEquals("Single substrate has expected coefficient", expectedSubstrateCoefficients[0],
          rxn.getSubstrateCoefficient(newSubstrates[0]));
  
      Long[] newProducts = rxn.getProducts();
      assertEquals("One Product remains after desalting", 1, newProducts.length);
      assertEquals("Single product has expected inchi", expectedDesaltingInchi2,
          mockAPI.getWrittenChemicals().get(newProducts[0]).getInChI());
      assertEquals("Single product has expected coefficient", expectedProductCoefficients[0],
          rxn.getProductCoefficient(newProducts[0]));
    }
  
    @Test
    public void testChemicalsWithMultiplierThatDesaltToTheSameMoleculeGetSummed() throws Exception {
      List<Reaction> testReactions = new ArrayList<>();
  
      Map<Long, String> idToInchi = new HashMap<>();
  
      // The two inchis below, once desalted, map to InChI=1S/CH2O2/c2-1-3/h1H,(H,2,3)
      idToInchi.put(1L, "InChI=1S/2C5H5.F6P.Fe/c2*1-2-4-5-3-1;1-7(2,3,4,5)6;/h2*1-5H;;/q3*-1;+3");
      idToInchi.put(2L, "InChI=1S/2C5H5.Fe/c2*1-2-4-5-3-1;/h2*1-5H;/q2*-1;+2");
      idToInchi.put(3L, "InChI=1S/6CN.Fe.3K/c6*1-2;;;;/q;;;;;;-3;3*+1");
      idToInchi.put(4L, "InChI=1S/8CN.W/c8*1-2;/q;;;;;;;;-3");
  
      String expectedDesaltingInchi1 = "InChI=1S/C5H5/c1-2-4-5-3-1/h1-5H/q-1";
      String expectedDesaltingInchi2 = "InChI=1S/CN/c1-2";
  
      Long[] substrates1 = {1L, 2L};
      Long[] products = {3L, 4L};
  
      Integer[] substrateCoefficients = {3, 7};
      Integer[] productCoefficients = {5, 11};
  
      Integer[] expectedSubstrateCoefficients = {3 * 2 + 7 * 2};
      Integer[] expectedProductCoefficients = {5 * 6 + 11 * 8};
  
      Reaction testReaction1 =
          utilsObject.makeTestReaction(substrates1, products, substrateCoefficients, productCoefficients, true);
  
      testReactions.add(testReaction1);
  
      MockedNoSQLAPI mockAPI = new MockedNoSQLAPI();
      mockAPI.installMocks(testReactions, utilsObject.SEQUENCES, utilsObject.ORGANISM_NAMES, idToInchi);
  
      NoSQLAPI mockNoSQLAPI = mockAPI.getMockNoSQLAPI();
  
      ReactionDesalter testReactionDesalter = new ReactionDesalter(mockNoSQLAPI);
      testReactionDesalter.init();
      testReactionDesalter.run();
  
      assertEquals("One desalted reaction should be written", 1,
          mockAPI.getWrittenReactions().size());
  
      Reaction rxn = mockAPI.getWrittenReactions().get(0);
  
      Long[] newSubstrates = rxn.getSubstrates();
      assertEquals("One substrate remains after desalting", 1, newSubstrates.length);
      assertEquals("Single substrate has expected inchi", expectedDesaltingInchi1,
          mockAPI.getWrittenChemicals().get(newSubstrates[0]).getInChI());
      assertEquals("Single substrate has expected coefficient", expectedSubstrateCoefficients[0],
          rxn.getSubstrateCoefficient(newSubstrates[0]));
  
      Long[] newProducts = rxn.getProducts();
      assertEquals("One Product remains after desalting", 1, newProducts.length);
      assertEquals("Single product has expected inchi", expectedDesaltingInchi2,
          mockAPI.getWrittenChemicals().get(newProducts[0]).getInChI());
      assertEquals("Single product has expected coefficient", expectedProductCoefficients[0],
          rxn.getProductCoefficient(newProducts[0]));
    }
  }
  