/* * Copyright 2013 Gordon Burgett and individual contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xflatdb.xflat.db; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.xflatdb.xflat.Cursor; import org.xflatdb.xflat.KeyNotFoundException; import org.xflatdb.xflat.convert.ConversionService; import org.xflatdb.xflat.convert.DefaultConversionService; import org.xflatdb.xflat.convert.converters.JDOMConverters; import org.xflatdb.xflat.convert.converters.StringConverters; import org.xflatdb.xflat.query.XPathQuery; import org.xflatdb.xflat.query.XPathUpdate; import org.hamcrest.Matchers; import org.jdom2.Element; import org.jdom2.xpath.XPathFactory; import org.junit.After; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import static org.mockito.Mockito.*; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.xflatdb.xflat.XFlatConstants; /** * * @author gordon */ public class ElementTableTest { public ElementTableTest() { } ConversionService conversionService; Engine engine; IdGenerator idGenerator; private XPathFactory xpath; ElementTable getInstance(){ ElementTable ret = new ElementTable("test"); ret.setIdGenerator(idGenerator); ret.setEngineProvider(new EngineProvider() { @Override public Engine provideEngine() { return engine; } }); return ret; }; @Before public void setUp() { conversionService = new DefaultConversionService(); StringConverters.registerTo(conversionService); JDOMConverters.registerTo(conversionService); engine = mock(Engine.class); idGenerator = mock(IdGenerator.class); xpath = XPathFactory.instance(); } @After public void tearDown() { } private String getId(Element data){ return data.getAttributeValue("id", XFlatConstants.xFlatNs); } private void setId(Element e, String id){ if(id == null){ e.removeAttribute("id", XFlatConstants.xFlatNs); return; } e.setAttribute("id", id, XFlatConstants.xFlatNs); } @Test public void testInsert_GeneratesId() throws Exception { System.out.println("testInsert_GeneratesId"); Element foo = new Element("foo"); foo.setContent(new Element("fooInt").setText("17")); String fooId = "testId"; when(idGenerator.generateNewId(String.class)) .thenReturn(fooId); when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); //ACT getInstance().insert(foo); //ASSERT ArgumentCaptor<Element> dataCaptor = ArgumentCaptor.forClass(Element.class); verify(engine).insertRow(argThat(Matchers.equalTo(fooId)), dataCaptor.capture()); Element data = dataCaptor.getValue(); assertEquals("Should have set ID", fooId, getId(data)); assertEquals("Data should be a foo", "foo", data.getName()); assertEquals("Should have stored foo in element", "17", data.getChild("fooInt").getValue()); }//end testInsert_GeneratesId @Test public void testInsert_HasId_IdNotModified() throws Exception { System.out.println("testInsert_HasId_IdNotModified"); Element foo = new Element("foo"); foo.setContent(new Element("fooInt").setText("17")); setId(foo, "fooId"); when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); //ACT getInstance().insert(foo); //ASSERT ArgumentCaptor<Element> dataCaptor = ArgumentCaptor.forClass(Element.class); verify(engine).insertRow(argThat(Matchers.equalTo("fooId")), dataCaptor.capture()); verify(idGenerator, never()).generateNewId(any(Class.class)); Element data = dataCaptor.getValue(); assertEquals("Should have kept ID", "fooId", getId(data)); assertEquals("Data should be a foo", "foo", data.getName()); assertEquals("Should have stored foo in element", "17", data.getChild("fooInt").getValue()); }//end testInsert_HasId_IdNotModified @Test public void testFind_Foo_IdDoesNotExist_ReturnsNull() throws Exception { System.out.println("testFind_Foo_IdDoesNotExist_ReturnsNull"); String fooId = "test id"; when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); Element inDb = getInstance().find(fooId); assertNull("Nothing in db, should find nothing", inDb); verify(engine).readRow(fooId); }//end testFind_Foo_IdDoesNotExist_ReturnsNull @Test public void testFind_Foo_GetsElement_DeserializesAndSetsId() throws Exception { System.out.println("testFind_Foo_GetsElement_DeserializesAndSetsId"); String fooId = "test id"; when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); when(idGenerator.stringToId(any(String.class), eq(String.class))) .thenAnswer(byReturningFirstParam()); Element inDb = new Element("foo"); setId(inDb, fooId); inDb.addContent(new Element("fooInt").setText("32")); when(engine.readRow(fooId)) .thenReturn(inDb); //ACT Element found = getInstance().find(fooId); //ASSERT assertNotNull("Should have found some data", found); assertEquals("Should have found right data", "32", found.getChild("fooInt").getText()); assertEquals("Should have set ID", fooId, getId(found)); }//end testFind_Foo_GetsElement_DeserializesAndSetsId @Test public void testFindOne_Foo_DeserializesAndClosesCursor() throws Exception { System.out.println("testFindOne_Foo_DeserializesAndClosesCursor"); String fooId = "test id"; when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); Element inDb = new Element("foo"); setId(inDb, fooId); inDb.addContent(new Element("fooInt").setText("32")); XPathQuery query = XPathQuery.eq(xpath.compile("foo/fooInt"), 32); Cursor<Element> mockCursor = getMockCursor(inDb); when(engine.queryTable(query)) .thenReturn(mockCursor); //ACT Element found = getInstance().findOne(query); //ASSERT assertNotNull("Should have found one", found); assertEquals("Should have got right one", "32", found.getChild("fooInt").getText()); verify(mockCursor).close(); }//end testFindOne_Foo_DeserializesAndClosesCursor @Test public void testFind_Foo_ReturnsConvertingCursor() throws Exception { System.out.println("testFind_Foo_ReturnsConvertingCursor"); when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); when(idGenerator.stringToId(any(String.class), eq(String.class))) .thenAnswer(byReturningFirstParam()); Element inDb1 = new Element("foo").addContent(new Element("fooInt").setText("32")); setId(inDb1, "id 1"); Element inDb2 = new Element("foo").addContent(new Element("fooInt").setText("33")); setId(inDb2, "id 2"); XPathQuery query = XPathQuery.gte(xpath.compile("foo/fooInt"), 32); Cursor<Element> mockCursor = getMockCursor(inDb1, inDb2); when(engine.queryTable(query)) .thenReturn(mockCursor); try (Cursor<Element> fooCursor = getInstance().find(query)) { Iterator<Element> i = fooCursor.iterator(); Element found1 = i.next(); Element found2 = i.next(); assertEquals("Should have gotten correct values", "32", found1.getChild("fooInt").getText()); assertEquals("Should have gotten correct values", "33", found2.getChild("fooInt").getText()); assertEquals("Should have gotten correct IDs", "id 1", getId(found1)); assertEquals("Should have gotten correct IDs", "id 2", getId(found2)); //verify engine cursor is closed when we close returned cursor verify(mockCursor, never()).close(); } verify(mockCursor).close(); }//end testFind_Foo_ReturnsConvertingCursor @Test public void testFindAll_Foo_ConvertsAllToList() throws Exception { System.out.println("testFindAll_Foo_ConvertsAllToList"); when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); when(idGenerator.stringToId(any(String.class), eq(String.class))) .thenAnswer(byReturningFirstParam()); Element inDb1 = new Element("foo").addContent(new Element("fooInt").setText("32")); setId(inDb1, "id 1"); Element inDb2 = new Element("foo").addContent(new Element("fooInt").setText("33")); setId(inDb2, "id 2"); XPathQuery query = XPathQuery.gte(xpath.compile("foo/fooInt"), 32); Cursor<Element> mockCursor = getMockCursor(inDb1, inDb2); when(engine.queryTable(query)) .thenReturn(mockCursor); //ACT List<Element> fooList = getInstance().findAll(query); //ASSERT Element found1 = fooList.get(0); Element found2 = fooList.get(1); assertEquals("Should have gotten correct values", "32", found1.getChild("fooInt").getText()); assertEquals("Should have gotten correct values", "33", found2.getChild("fooInt").getText()); assertEquals("Should have gotten correct IDs", "id 1", getId(found1)); assertEquals("Should have gotten correct IDs", "id 2", getId(found2)); }//end testFindAll_Foo_ConvertsAllToList @Test public void testReplace_Foo_ReplacesRowWithSameId() throws Exception { System.out.println("testReplace_Foo_ReplacesRowWithSameId"); when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); Element replacement = new Element("foo"); replacement.setContent(new Element("fooInt").setText("17")); setId(replacement, "test id"); //act this.getInstance().replace(replacement); //assert ArgumentCaptor<Element> captor = ArgumentCaptor.forClass(Element.class); verify(engine).replaceRow(eq("test id"), captor.capture()); assertEquals("Should have replaced with correct data", "17", captor.getValue().getChild("fooInt").getText()); }//end testReplace_Foo_ReplacesRowWithSameId @Test public void testReplace_Foo_NoId_ThrowsKeyNotFoundException() throws Exception { System.out.println("testReplace_Foo_NoId_ThrowsKeyNotFoundException"); when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); Element replacement = new Element("foo"); replacement.setContent(new Element("fooInt").setText("17")); setId(replacement, null); boolean didThrow = false; try { //ACT this.getInstance().replace(replacement); } catch (KeyNotFoundException expected) { didThrow = true; } assertTrue("Should have thrown KeyNotFoundException", didThrow); }//end testReplace_Foo_NoId_ThrowsKeyNotFoundException @Test public void testReplaceOne_Foo_FindsAndReplaces() throws Exception { System.out.println("testReplaceOne_Foo_FindsAndReplaces"); when(idGenerator.stringToId(anyString(), eq(String.class))) .thenAnswer(byReturningFirstParam()); Element existing = new Element("foo").addContent(new Element("fooInt").setText("17")); setId(existing, "test id 1"); XPathQuery query = XPathQuery.eq(xpath.compile("foo/fooInt"), 17); Cursor<Element> mockCursor = getMockCursor(existing); when(engine.queryTable(query)) .thenReturn(mockCursor); Element newFoo = new Element("foo"); newFoo.setContent(new Element("fooInt").setText("4")); setId(newFoo, "to be replaced"); //ACT getInstance().replaceOne(query, newFoo); //ASSERT ArgumentCaptor<Element> captor = ArgumentCaptor.forClass(Element.class); verify(engine).replaceRow(eq("test id 1"), captor.capture()); assertEquals("Should have replaced value", "4", captor.getValue().getChild("fooInt").getText()); assertEquals("Should have set ID on object", "test id 1", getId(newFoo)); }//end testReplaceOne_Foo_FindsAndReplaces @Test public void testReplaceOne_ConcurrentRemove_FindsSecond() throws Exception { System.out.println("testReplaceOne_ConcurrentRemove_FindsSecond"); when(idGenerator.stringToId(anyString(), eq(String.class))) .thenAnswer(byReturningFirstParam()); Element existing = new Element("foo").addContent(new Element("fooInt").setText("17")); setId(existing, "test id 1"); Element existing2 = new Element("foo").addContent(new Element("fooInt").setText("18")); setId(existing2, "test id 2"); XPathQuery query = XPathQuery.gte(xpath.compile("foo/fooInt"), 17); Cursor<Element> cursor1 = getMockCursor(existing); Cursor<Element> cursor2 = getMockCursor(existing2); when(engine.queryTable(query)) .thenReturn(cursor1, cursor2); doThrow(new KeyNotFoundException("test")) .when(engine).replaceRow(eq("test id 1"), any(Element.class)); Element newFoo = new Element("foo"); newFoo.setContent(new Element("fooInt").setText("4")); setId(newFoo, "to be replaced"); //ACT getInstance().replaceOne(query, newFoo); //ASSERT ArgumentCaptor<Element> captor = ArgumentCaptor.forClass(Element.class); verify(engine).replaceRow(eq("test id 2"), captor.capture()); assertEquals("Should have replaced value", "4", captor.getValue().getChild("fooInt").getText()); assertEquals("Should have set ID on object", "test id 2", getId(newFoo)); }//end testReplaceOne_ConcurrentRemove_FindsSecond @Test public void testUpsert_Foo_NoId_InsertsWithNewId() throws Exception { System.out.println("testUpsert_Foo_NoId_InsertsWithNewId"); when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); when(idGenerator.stringToId(any(String.class), eq(String.class))) .thenAnswer(byReturningFirstParam()); String id = "test id"; when(idGenerator.generateNewId(String.class)) .thenReturn(id); Element toUpsert = new Element("foo"); toUpsert.setContent(new Element("fooInt").setText("51")); //ACT boolean didInsert = this.getInstance().upsert(toUpsert); //ASSERT assertTrue("Should have inserted new foo", didInsert); ArgumentCaptor<Element> data = ArgumentCaptor.forClass(Element.class); verify(engine).insertRow(eq(id), data.capture()); assertEquals("Should have inserted correct data", "51", data.getValue().getChild("fooInt").getText()); }//end testUpsert_Foo_NoId_InsertsWithNewId @Test public void testUpsert_Foo_HasId_UpsertsWithId() throws Exception { System.out.println("testUpsert_Foo_HasId_UpsertsWithId"); when(idGenerator.idToString(anyObject())) .thenAnswer(byCallingToString()); when(idGenerator.stringToId(any(String.class), eq(String.class))) .thenAnswer(byReturningFirstParam()); String id = "test id"; Element toUpsert = new Element("foo"); toUpsert.setContent(new Element("fooInt").setText("51")); setId(toUpsert, id); //ACT boolean didInsert = this.getInstance().upsert(toUpsert); //ASSERT assertFalse("Should have gotten default from upsertRow", didInsert); ArgumentCaptor<Element> data = ArgumentCaptor.forClass(Element.class); verify(engine).upsertRow(eq(id), data.capture()); assertEquals("Should have inserted correct data", "51", data.getValue().getChild("fooInt").getText()); verify(idGenerator, never()).generateNewId(any(Class.class)); }//end testUpsert_Foo_HasId_UpsertsWithId @Test public void testUpdate_Id_ConvertsId() throws Exception { System.out.println("testUpdate_Id_ConvertsId"); String id = "test id"; XPathUpdate update = XPathUpdate.set(xpath.compile("fooInt"), 32); when(engine.update(id, update)) .thenReturn(Boolean.TRUE); //ACT boolean didUpdate = this.getInstance().update(id, update); //ASSERT assertTrue("Should have invoked update to get true result", didUpdate); verify(engine).update(id, update); }//end testUpdate_Id_ConvertsId @Test public void testUpdate_Query_Passthrough() throws Exception { System.out.println("testUpdate_Query_Passthrough"); XPathQuery query = XPathQuery.eq(xpath.compile("fooInt"), 17); XPathUpdate update = XPathUpdate.set(xpath.compile("fooInt"), 35); when(engine.update(query, update)) .thenReturn(32); //ACT int rowsUpdated = this.getInstance().update(query, update); //ASSERT assertEquals("Should have gotten the result of our stubbed call", 32, rowsUpdated); }//end testUpdate_Query_Passthrough @Test public void testDelete_Id_ConvertsId() throws Exception { System.out.println("testDelete_Id_ConvertsId"); String id = "test id"; //ACT this.getInstance().delete(id); //ASSERT verify(engine).deleteRow(id); }//end testDelete_Id_ConvertsId @Test public void testDeleteAll_Passthrough() throws Exception { System.out.println("testDeleteAll_Passthrough"); XPathQuery query = XPathQuery.eq(xpath.compile("foo/fooInt"), 17); //ACT this.getInstance().deleteAll(query); //ASSERT verify(engine).deleteAll(query); }//end testDeleteAll_Passthrough private Answer<String> byCallingToString(){ return new Answer<String>(){ @Override public String answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); return args[0].toString(); } }; } private Answer<Object> byReturningFirstParam(){ return new Answer<Object>(){ @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); return args[0]; } }; } private Cursor<Element> getMockCursor(Element... elements){ return getMockCursor(Arrays.asList(elements)); } private Cursor<Element> getMockCursor(final Iterable<Element> elements){ Cursor<Element> ret = mock(Cursor.class); when(ret.iterator()) .thenReturn(elements.iterator()); return ret; } }