/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.xpn.xwiki.store.hibernate.query; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Provider; import org.hibernate.SQLQuery; import org.hibernate.Session; import org.hibernate.cfg.Configuration; import org.hibernate.engine.NamedSQLQueryDefinition; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.xwiki.component.manager.ComponentManager; import org.xwiki.component.util.DefaultParameterizedType; import org.xwiki.context.Execution; import org.xwiki.context.ExecutionContext; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.query.QueryFilter; import org.xwiki.query.WrappingQuery; import org.xwiki.query.internal.DefaultQuery; import org.xwiki.security.authorization.ContextualAuthorizationManager; import org.xwiki.security.authorization.Right; import org.xwiki.test.mockito.MockitoComponentMockingRule; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.store.XWikiHibernateBaseStore; import com.xpn.xwiki.store.XWikiHibernateStore; import com.xpn.xwiki.store.hibernate.HibernateSessionFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; /** * Unit tests for {@link HqlQueryExecutor} * * @version $Id: f38e73f5fd2cf755c1baf3c2c3c833da5a3233a7 $ */ public class HqlQueryExecutorTest { @Rule public MockitoComponentMockingRule<HqlQueryExecutor> mocker = new MockitoComponentMockingRule<>( HqlQueryExecutor.class); private ContextualAuthorizationManager authorization; private boolean hasProgrammingRight; /** * The component under test. */ private HqlQueryExecutor executor; private XWikiHibernateStore store; @Before public void before() throws Exception { HibernateSessionFactory sessionFactory = this.mocker.getInstance(HibernateSessionFactory.class); when(sessionFactory.getConfiguration()).thenReturn(new Configuration()); this.executor = this.mocker.getComponentUnderTest(); this.authorization = this.mocker.getInstance(ContextualAuthorizationManager.class); when(this.authorization.hasAccess(Right.PROGRAM)).then(new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { return hasProgrammingRight; } }); this.hasProgrammingRight = true; // Actual Hibernate query Execution execution = this.mocker.getInstance(Execution.class); ExecutionContext executionContext = mock(ExecutionContext.class); when(execution.getContext()).thenReturn(executionContext); XWikiContext xwikiContext = mock(XWikiContext.class); when(executionContext.getProperty(XWikiContext.EXECUTIONCONTEXT_KEY)).thenReturn(xwikiContext); when(xwikiContext.getWikiId()).thenReturn("currentwikid"); com.xpn.xwiki.XWiki xwiki = mock(com.xpn.xwiki.XWiki.class); when(xwikiContext.getWiki()).thenReturn(xwiki); this.store = mock(XWikiHibernateStore.class); when(xwiki.getHibernateStore()).thenReturn(store); } private void execute(String statement, Boolean withProgrammingRights) throws QueryException { this.hasProgrammingRight = withProgrammingRights != null ? withProgrammingRights : true; DefaultQuery query = new DefaultQuery(statement, Query.HQL, this.executor); if (withProgrammingRights != null) { query.checkCurrentAuthor(true); } this.executor.execute(query); } private void executeNamed(String name, Boolean withProgrammingRights) throws QueryException { this.hasProgrammingRight = withProgrammingRights; DefaultQuery query = new DefaultQuery(name, this.executor); if (withProgrammingRights != null) { query.checkCurrentAuthor(true); } this.executor.execute(query); } // Tests @Test public void completeShortStatementWhenEmpty() { assertEquals("select doc.fullName from XWikiDocument doc ", this.executor.completeShortFormStatement("")); } @Test public void completeShortStatementStartingWithWhere() { assertEquals("select doc.fullName from XWikiDocument doc where doc.author='XWiki.Admin'", this.executor.completeShortFormStatement("where doc.author='XWiki.Admin'")); } @Test public void completeShortStatementStartingWithFrom() { assertEquals("select doc.fullName from XWikiDocument doc , BaseObject obj where doc.fullName=obj.name " + "and obj.className='XWiki.MyClass'", this.executor.completeShortFormStatement(", BaseObject obj where " + "doc.fullName=obj.name and obj.className='XWiki.MyClass'")); } @Test public void completeShortStatementStartingWithOrderBy() { assertEquals("select doc.fullName from XWikiDocument doc order by doc.date desc", this.executor.completeShortFormStatement("order by doc.date desc")); } @Test public void completeShortStatementPassingAnAlreadyCompleteQuery() { assertEquals("select doc.fullName from XWikiDocument doc order by doc.date desc", this.executor .completeShortFormStatement("select doc.fullName from XWikiDocument doc order by doc.date desc")); } @Test public void completeShortStatementPassingAQueryOnSomethingElseThanADocument() { assertEquals("select lock.docId from XWikiLock as lock ", this.executor.completeShortFormStatement("select lock.docId from XWikiLock as lock ")); } @Test public void setNamedParameter() { org.hibernate.Query query = mock(org.hibernate.Query.class); String name = "abc"; Date value = new Date(); this.executor.setNamedParameter(query, name, value); verify(query).setParameter(name, value); } @Test public void setNamedParameterList() { org.hibernate.Query query = mock(org.hibernate.Query.class); String name = "foo"; List<String> value = Arrays.asList("one", "two", "three"); this.executor.setNamedParameter(query, name, value); verify(query).setParameterList(name, value); } @Test public void setNamedParameterArray() { org.hibernate.Query query = mock(org.hibernate.Query.class); String name = "bar"; Integer[] value = new Integer[] { 1, 2, 3 }; this.executor.setNamedParameter(query, name, value); verify(query).setParameterList(name, value); } @Test public void populateParameters() { org.hibernate.Query hquery = mock(org.hibernate.Query.class); Query query = mock(Query.class); int offset = 13; when(query.getOffset()).thenReturn(offset); int limit = 7; when(query.getLimit()).thenReturn(limit); Map<String, Object> namedParameters = new HashMap<String, Object>(); namedParameters.put("alice", 10); List<String> listValue = Collections.singletonList("yellow"); namedParameters.put("bob", listValue); when(query.getNamedParameters()).thenReturn(namedParameters); this.executor.populateParameters(hquery, query); verify(hquery).setFirstResult(offset); verify(hquery).setMaxResults(limit); verify(hquery).setParameter("alice", 10); verify(hquery).setParameterList("bob", listValue); } @Test public void executeWhenStoreException() throws Exception { XWikiException exception = mock(XWikiException.class); when(exception.getMessage()).thenReturn("nestedmessage"); when(this.store.executeRead(any(XWikiContext.class), any(XWikiHibernateBaseStore.HibernateCallback.class))) .thenThrow(exception); try { execute("statement", null); fail("Should have thrown an exception here"); } catch (QueryException expected) { assertEquals("Exception while executing query. Query statement = [statement]", expected.getMessage()); // Verify nested exception! assertEquals("nestedmessage", expected.getCause().getMessage()); } } @Test @SuppressWarnings("unchecked") public void createNamedNativeHibernateQuery() throws Exception { DefaultQuery query = new DefaultQuery("queryName", this.executor); Session session = mock(Session.class); SQLQuery sqlQuery = mock(SQLQuery.class); when(session.getNamedQuery(query.getStatement())).thenReturn(sqlQuery); when(sqlQuery.getQueryString()).thenReturn("foo"); // Add a Query Filter to verify it's called and can change the statement QueryFilter filter = mock(QueryFilter.class); query.addFilter(filter); when(filter.filterStatement("foo", "sql")).thenReturn("bar"); when(filter.filterQuery(any(Query.class))).then(returnsFirstArg()); SQLQuery finalQuery = mock(SQLQuery.class); when(session.createSQLQuery("bar")).thenReturn(finalQuery); NamedSQLQueryDefinition definition = mock(NamedSQLQueryDefinition.class); when(definition.getResultSetRef()).thenReturn("someResultSet"); HibernateSessionFactory sessionFactory = this.mocker.getInstance(HibernateSessionFactory.class); sessionFactory.getConfiguration().getNamedSQLQueries().put(query.getStatement(), definition); assertSame(finalQuery, this.executor.createHibernateQuery(session, query)); verify(finalQuery).setResultSetMapping(definition.getResultSetRef()); } @Test public void createHibernateQueryWhenFilter() throws Exception { Session session = mock(Session.class); DefaultQuery query = new DefaultQuery("where doc.space='Main'", Query.HQL, this.executor); // Add a Query Filter to verify it's called and can change the statement. // We also verify that QueryFilter#filterStatement() is called before QueryFilter#filterQuery() QueryFilter filter = mock(QueryFilter.class); query.addFilter(filter); when(filter.filterStatement("select doc.fullName from XWikiDocument doc where doc.space='Main'", Query.HQL)).thenReturn("select doc.fullName from XWikiDocument doc where doc.space='Main2'"); when(filter.filterQuery(any(Query.class))).thenReturn(new WrappingQuery(query) { @Override public String getStatement() { return "select doc.fullName from XWikiDocument doc where doc.space='Main3'"; } }); this.executor.createHibernateQuery(session, query); // The test is here! verify(session).createQuery("select doc.fullName from XWikiDocument doc where doc.space='Main3'"); } @Test public void createHibernateQueryAutomaticallyAddEscapeLikeParametersFilterWhenQueryParameter() throws Exception { Session session = mock(Session.class); DefaultQuery query = new DefaultQuery("where space like :space", Query.HQL, this.executor); query.bindValue("space").literal("test"); QueryFilter filter = mock(QueryFilter.class); when(filter.filterStatement(anyString(), anyString())).then(returnsFirstArg()); when(filter.filterQuery(any(Query.class))).then(returnsFirstArg()); ComponentManager cm = this.mocker.getInstance(ComponentManager.class, "context"); when(cm.getInstance(QueryFilter.class, "escapeLikeParameters")).thenReturn(filter); when(session.createQuery(anyString())).thenReturn(mock(org.hibernate.Query.class)); this.executor.createHibernateQuery(session, query); // The test is here! We verify that the filter has been called even though we didn't explicitly add it to the // query, i.e. that it's been added automatically verify(filter).filterQuery(any(Query.class)); } @Test public void executeShortWhereHQLQueryWithProgrammingRights() throws QueryException { execute("where doc.space='Main'", true); } @Test public void executeShortFromHQLQueryWithProgrammingRights() throws QueryException { execute(", BaseObject as obj", true); } @Test public void executeCompleteHQLQueryWithProgrammingRights() throws QueryException { execute("select u from XWikiDocument as doc", true); } @Test public void executeNamedQueryWithProgrammingRights() throws QueryException { executeNamed("somename", true); } @Test public void executeShortWhereHQLQueryWithoutProgrammingRights() throws QueryException { execute("where doc.space='Main'", false); } @Test public void executeShortFromHQLQueryWithoutProgrammingRights() throws QueryException { execute(", BaseObject as obj", false); } // Not allowed @Test public void executeWhenNotAllowedSelect() throws Exception { try { execute("select notallowed.name from NotAllowedTable notallowed", false); fail("Should have thrown an exception here"); } catch (QueryException expected) { assertEquals("The query requires programming right." + " Query statement = [select notallowed.name from NotAllowedTable notallowed]", expected.getMessage()); } } @Test public void executeDeleteWithoutProgrammingRight() throws Exception { try { execute("delete from XWikiDocument as doc", false); fail("Should have thrown an exception here"); } catch (QueryException expected) { assertEquals("The query requires programming right. Query statement = [delete from XWikiDocument as doc]", expected.getMessage()); } } @Test public void executeNamedQueryWithoutProgrammingRight() throws Exception { try { executeNamed("somename", false); fail("Should have thrown an exception here"); } catch (QueryException expected) { assertEquals("Named queries requires programming right. Named query = [somename]", expected.getMessage()); } } @Test public void executeUpdateWithoutProgrammingRight() throws Exception { try { execute("update XWikiDocument set name='name'", false); fail("Should have thrown an exception here"); } catch (QueryException expected) { assertEquals( "The query requires programming right. Query statement = [update XWikiDocument set name='name']", expected.getMessage()); } } }