From 91a64d837bd83d293ea98a9aa630cc7ba0aadad5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 6 Jun 2017 17:54:47 +0200 Subject: [PATCH] SONAR-9191 cache rule in RuleFinder during startup --- .../platformlevel/PlatformLevel4.java | 4 +- .../platformlevel/PlatformLevelStartup.java | 2 + .../sonar/server/rule/CachingRuleFinder.java | 196 ++++++++ .../sonar/server/rule/DefaultRuleFinder.java | 2 +- .../org/sonar/server/rule/RegisterRules.java | 9 +- .../server/rule/WebServerRuleFinder.java | 41 ++ .../server/rule/WebServerRuleFinderImpl.java | 84 ++++ .../server/rule/CachingRuleFinderTest.java | 423 ++++++++++++++++++ .../sonar/server/rule/RegisterRulesTest.java | 15 +- .../rule/WebServerRuleFinderImplTest.java | 65 +++ 10 files changed, 830 insertions(+), 11 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/rule/CachingRuleFinder.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/rule/WebServerRuleFinderImplTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 8805cb297f5..d6e90fdd7bb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -160,11 +160,11 @@ import org.sonar.server.qualityprofile.ws.QProfilesWsModule; import org.sonar.server.qualityprofile.ws.SearchDataLoader; import org.sonar.server.root.ws.RootWsModule; import org.sonar.server.rule.CommonRuleDefinitionsImpl; -import org.sonar.server.rule.DefaultRuleFinder; import org.sonar.server.rule.DeprecatedRulesDefinitionLoader; import org.sonar.server.rule.RuleCreator; import org.sonar.server.rule.RuleDefinitionsLoader; import org.sonar.server.rule.RuleUpdater; +import org.sonar.server.rule.WebServerRuleFinderImpl; import org.sonar.server.rule.index.RuleIndexDefinition; import org.sonar.server.rule.index.RuleIndexer; import org.sonar.server.rule.ws.ActiveRuleCompleter; @@ -286,7 +286,7 @@ public class PlatformLevel4 extends PlatformLevel { RuleIndexer.class, AnnotationRuleParser.class, XMLRuleParser.class, - DefaultRuleFinder.class, + WebServerRuleFinderImpl.class, DeprecatedRulesDefinitionLoader.class, RuleDefinitionsLoader.class, CommonRuleDefinitionsImpl.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index e04676ae3fb..9bbfd549765 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -32,6 +32,7 @@ import org.sonar.server.qualityprofile.DefinedQProfileLoader; import org.sonar.server.qualityprofile.MassRegisterQualityProfiles; import org.sonar.server.qualityprofile.RegisterQualityProfiles; import org.sonar.server.rule.RegisterRules; +import org.sonar.server.rule.WebServerRuleFinder; import org.sonar.server.startup.DeleteOldAnalysisReportsFromFs; import org.sonar.server.startup.DisplayLogOnDeprecatedProjects; import org.sonar.server.startup.GeneratePluginIndex; @@ -83,6 +84,7 @@ public class PlatformLevelStartup extends PlatformLevel { getOptional(IndexerStartupTask.class).ifPresent(IndexerStartupTask::execute); get(ServerLifecycleNotifier.class).notifyStart(); get(ProcessCommandWrapper.class).notifyOperational(); + get(WebServerRuleFinder.class).stopCaching(); } }); diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/CachingRuleFinder.java b/server/sonar-server/src/main/java/org/sonar/server/rule/CachingRuleFinder.java new file mode 100644 index 00000000000..ff11d30b20c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/CachingRuleFinder.java @@ -0,0 +1,196 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.rule; + +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Ordering; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rule.RuleStatus; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.rules.RulePriority; +import org.sonar.api.rules.RuleQuery; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.rule.RuleDefinitionDto; +import org.sonar.db.rule.RuleDto; +import org.sonar.db.rule.RuleParamDto; +import org.sonar.markdown.Markdown; + +import static com.google.common.collect.Lists.newArrayList; +import static org.sonar.core.util.stream.MoreCollectors.toSet; +import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; + +/** + * A {@link RuleFinder} implementation that retrieves all rule definitions and their parameter when instantiated, cache + * them in memory and provide implementation of {@link RuleFinder}'s method which only read from this data in memory. + */ +public class CachingRuleFinder implements RuleFinder { + + private static final Ordering> FIND_BY_QUERY_ORDER = Ordering.natural().reverse().onResultOf(entry -> entry.getKey().getUpdatedAt()); + + private final Map rulesByRuleDefinition; + private final Map rulesById; + private final Map rulesByKey; + + public CachingRuleFinder(DbClient dbClient) { + try (DbSession dbSession = dbClient.openSession(false)) { + this.rulesByRuleDefinition = buildRulesByRuleDefinitionDto(dbClient, dbSession); + this.rulesById = this.rulesByRuleDefinition.entrySet().stream() + .collect(uniqueIndex(entry -> entry.getKey().getId(), Map.Entry::getValue)); + this.rulesByKey = this.rulesByRuleDefinition.entrySet().stream() + .collect(uniqueIndex(entry -> entry.getKey().getKey(), Map.Entry::getValue)); + } + } + + private static Map buildRulesByRuleDefinitionDto(DbClient dbClient, DbSession dbSession) { + List dtos = dbClient.ruleDao().selectAllDefinitions(dbSession); + Set ruleKeys = dtos.stream().map(RuleDefinitionDto::getKey).collect(toSet(dtos.size())); + ListMultimap ruleParamsByRuleId = retrieveRuleParameters(dbClient, dbSession, ruleKeys); + Map rulesByDefinition = new HashMap<>(dtos.size()); + for (RuleDefinitionDto definition : dtos) { + rulesByDefinition.put(definition, toRule(definition, ruleParamsByRuleId.get(definition.getId()))); + } + return ImmutableMap.copyOf(rulesByDefinition); + } + + private static ImmutableListMultimap retrieveRuleParameters(DbClient dbClient, DbSession dbSession, Set ruleKeys) { + if (ruleKeys.isEmpty()) { + return ImmutableListMultimap.of(); + } + return dbClient.ruleDao().selectRuleParamsByRuleKeys(dbSession, ruleKeys) + .stream() + .collect(MoreCollectors.index(RuleParamDto::getRuleId)); + } + + @Override + @Deprecated + @CheckForNull + public Rule findById(int ruleId) { + return rulesById.get(ruleId); + } + + @Override + @CheckForNull + public Rule findByKey(@Nullable String repositoryKey, @Nullable String key) { + if (repositoryKey == null || key == null) { + return null; + } + return findByKey(RuleKey.of(repositoryKey, key)); + } + + @Override + @CheckForNull + public Rule findByKey(RuleKey key) { + return rulesByKey.get(key); + } + + @Override + @CheckForNull + public Rule find(@Nullable RuleQuery query) { + if (query == null) { + return null; + } + + return rulesByRuleDefinition.entrySet().stream() + .filter(entry -> matchQuery(entry.getKey(), query)) + .sorted(FIND_BY_QUERY_ORDER) + .map(Map.Entry::getValue) + .findFirst() + .orElse(null); + } + + @Override + public Collection findAll(@Nullable RuleQuery query) { + if (query == null) { + return Collections.emptyList(); + } + return rulesByRuleDefinition.entrySet().stream() + .filter(entry -> matchQuery(entry.getKey(), query)) + .sorted(FIND_BY_QUERY_ORDER) + .map(Map.Entry::getValue) + .collect(MoreCollectors.toList()); + } + + private static boolean matchQuery(RuleDefinitionDto ruleDefinition, RuleQuery ruleQuery) { + if (RuleStatus.REMOVED.equals(ruleDefinition.getStatus())) { + return false; + } + String repositoryKey = ruleQuery.getRepositoryKey(); + if (ruleQuery.getRepositoryKey() != null && !repositoryKey.equals(ruleDefinition.getRepositoryKey())) { + return false; + } + String key = ruleQuery.getKey(); + if (key != null && !key.equals(ruleDefinition.getRuleKey())) { + return false; + } + String configKey = ruleQuery.getConfigKey(); + return configKey == null || configKey.equals(ruleDefinition.getConfigKey()); + } + + private static Rule toRule(RuleDefinitionDto ruleDefinition, List params) { + String severity = ruleDefinition.getSeverityString(); + String description = ruleDefinition.getDescription(); + RuleDto.Format descriptionFormat = ruleDefinition.getDescriptionFormat(); + + org.sonar.api.rules.Rule apiRule = new org.sonar.api.rules.Rule(); + apiRule + .setName(ruleDefinition.getName()) + .setLanguage(ruleDefinition.getLanguage()) + .setKey(ruleDefinition.getRuleKey()) + .setConfigKey(ruleDefinition.getConfigKey()) + .setIsTemplate(ruleDefinition.isTemplate()) + .setCreatedAt(new Date(ruleDefinition.getCreatedAt())) + .setUpdatedAt(new Date(ruleDefinition.getUpdatedAt())) + .setRepositoryKey(ruleDefinition.getRepositoryKey()) + .setSeverity(severity != null ? RulePriority.valueOf(severity) : null) + .setStatus(ruleDefinition.getStatus().name()) + .setTags(ruleDefinition.getSystemTags().toArray(new String[ruleDefinition.getSystemTags().size()])) + .setId(ruleDefinition.getId()); + if (description != null && descriptionFormat != null) { + if (RuleDto.Format.HTML.equals(descriptionFormat)) { + apiRule.setDescription(description); + } else { + apiRule.setDescription(Markdown.convertToHtml(description)); + } + } + + List apiParams = newArrayList(); + for (RuleParamDto param : params) { + apiParams.add(new org.sonar.api.rules.RuleParam(apiRule, param.getName(), param.getDescription(), param.getType()) + .setDefaultValue(param.getDefaultValue())); + } + apiRule.setParams(apiParams); + + return apiRule; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRuleFinder.java b/server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRuleFinder.java index ffe7267bc91..d6a276acc5d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRuleFinder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRuleFinder.java @@ -46,7 +46,7 @@ import org.sonar.server.organization.DefaultOrganizationProvider; import static com.google.common.collect.Lists.newArrayList; /** - * Will be removed in the future. Please use {@link org.sonar.server.rule.RuleService} + * Will be removed in the future. */ public class DefaultRuleFinder implements RuleFinder { diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java b/server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java index a9060ea975d..76643f136a3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java @@ -77,9 +77,11 @@ public class RegisterRules implements Startable { private final Languages languages; private final System2 system2; private final OrganizationFlags organizationFlags; + private final WebServerRuleFinder webServerRuleFinder; public RegisterRules(RuleDefinitionsLoader defLoader, RuleActivator ruleActivator, DbClient dbClient, RuleIndexer ruleIndexer, - ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, OrganizationFlags organizationFlags) { + ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, OrganizationFlags organizationFlags, + WebServerRuleFinder webServerRuleFinder) { this.defLoader = defLoader; this.ruleActivator = ruleActivator; this.dbClient = dbClient; @@ -88,6 +90,7 @@ public class RegisterRules implements Startable { this.languages = languages; this.system2 = system2; this.organizationFlags = organizationFlags; + this.webServerRuleFinder = webServerRuleFinder; } @Override @@ -105,7 +108,7 @@ public class RegisterRules implements Startable { RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key()); if (ruleDef.template() && orgsEnabled) { RuleDefinitionDto ruleDefinition = allRules.get(ruleKey); - if (ruleDefinition != null && ruleDefinition.getStatus() == RuleStatus.REMOVED) { + if (ruleDefinition != null && ruleDefinition.getStatus() == RuleStatus.REMOVED) { LOG.debug("Template rule {} kept removed, because organizations are enabled.", ruleKey); allRules.remove(ruleKey); } else { @@ -130,6 +133,8 @@ public class RegisterRules implements Startable { ruleIndexer.indexRuleDefinitions(keysToIndex); activeRuleIndexer.index(changes); profiler.stopDebug(); + + webServerRuleFinder.startCaching(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java b/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java new file mode 100644 index 00000000000..8a5ba6225ac --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinder.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.rule; + +import org.sonar.api.rules.RuleFinder; + +/** + * {@link RuleFinder} implementation that supports caching used by the Web Server. + *

+ * Caching is enabled right after loading of rules is done (see {@link RegisterRules}) and disabled + * once all startup tasks are done (see {@link org.sonar.server.platform.platformlevel.PlatformLevelStartup}). + *

+ */ +public interface WebServerRuleFinder extends RuleFinder { + /** + * Enable caching. + */ + void startCaching(); + + /** + * Disable caching. + */ + void stopCaching(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java new file mode 100644 index 00000000000..72b4e4dfc50 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/WebServerRuleFinderImpl.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.rule; + +import com.google.common.annotations.VisibleForTesting; +import java.util.Collection; +import javax.annotation.CheckForNull; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.rules.RuleQuery; +import org.sonar.db.DbClient; +import org.sonar.server.organization.DefaultOrganizationProvider; + +public class WebServerRuleFinderImpl implements WebServerRuleFinder { + private final DbClient dbClient; + private final RuleFinder defaultFinder; + @VisibleForTesting + RuleFinder delegate; + + public WebServerRuleFinderImpl(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider) { + this.dbClient = dbClient; + this.defaultFinder = new DefaultRuleFinder(dbClient, defaultOrganizationProvider); + this.delegate = this.defaultFinder; + } + + @Override + public void startCaching() { + this.delegate = new CachingRuleFinder(dbClient); + } + + @Override + public void stopCaching() { + this.delegate = this.defaultFinder; + } + + @Override + @CheckForNull + @Deprecated + public org.sonar.api.rules.Rule findById(int ruleId) { + return delegate.findById(ruleId); + } + + @Override + @CheckForNull + public Rule findByKey(String repositoryKey, String key) { + return delegate.findByKey(repositoryKey, key); + } + + @Override + @CheckForNull + public Rule findByKey(RuleKey key) { + return delegate.findByKey(key); + } + + @Override + @CheckForNull + public Rule find(RuleQuery query) { + return delegate.find(query); + } + + @Override + public Collection findAll(RuleQuery query) { + return delegate.findAll(query); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java new file mode 100644 index 00000000000..73992868bd4 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java @@ -0,0 +1,423 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.rule; + +import com.google.common.collect.ImmutableSet; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.Consumer; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleQuery; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.rule.RuleDao; +import org.sonar.db.rule.RuleDefinitionDto; +import org.sonar.db.rule.RuleParamDto; +import org.sonar.db.rule.RuleTesting; + +import static java.util.stream.Collectors.toList; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class CachingRuleFinderTest { + @org.junit.Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private DbClient dbClient = dbTester.getDbClient(); + + private AlwaysIncreasingSystem2 system2 = new AlwaysIncreasingSystem2(); + private RuleDefinitionDto[] ruleDefinitions; + private RuleParamDto[] ruleParams; + private CachingRuleFinder underTest; + + @Before() + public void setUp() throws Exception { + Consumer setUpdatedAt = rule -> rule.setUpdatedAt(system2.now()); + this.ruleDefinitions = new RuleDefinitionDto[] { + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt) + }; + this.ruleParams = Arrays.stream(ruleDefinitions) + .map(rule -> dbTester.rules().insertRuleParam(rule)) + .toArray(RuleParamDto[]::new); + + underTest = new CachingRuleFinder(dbClient); + + // delete all data from DB to ensure tests rely on cache exclusively + dbTester.executeUpdateSql("delete from rules"); + dbTester.executeUpdateSql("delete from rules_parameters"); + assertThat(dbTester.countRowsOfTable("rules")).isZero(); + assertThat(dbTester.countRowsOfTable("rules_parameters")).isZero(); + } + + @Test + public void constructor_reads_rules_from_DB() { + DbClient dbClient = mock(DbClient.class); + DbSession dbSession = mock(DbSession.class); + RuleDao ruleDao = mock(RuleDao.class); + when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); + when(dbClient.ruleDao()).thenReturn(ruleDao); + + new CachingRuleFinder(dbClient); + + verify(dbClient).openSession(anyBoolean()); + verify(ruleDao).selectAllDefinitions(dbSession); + verifyNoMoreInteractions(ruleDao); + } + + @Test + public void constructor_reads_parameters_from_DB() { + DbClient dbClient = mock(DbClient.class); + DbSession dbSession = mock(DbSession.class); + RuleDao ruleDao = mock(RuleDao.class); + when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); + when(dbClient.ruleDao()).thenReturn(ruleDao); + List ruleKeys = Arrays.asList(RuleKey.of("A", "B"), RuleKey.of("C", "D"), RuleKey.of("E", "F")); + when(ruleDao.selectAllDefinitions(dbSession)).thenReturn(ruleKeys.stream().map(RuleTesting::newRule).collect(toList())); + + new CachingRuleFinder(dbClient); + + verify(ruleDao).selectRuleParamsByRuleKeys(dbSession, ImmutableSet.copyOf(ruleKeys)); + } + + @Test + public void findById_returns_all_loaded_rules_by_id() { + for (int i = 0; i < ruleDefinitions.length; i++) { + RuleDefinitionDto ruleDefinition = ruleDefinitions[i]; + RuleParamDto ruleParam = ruleParams[i]; + + org.sonar.api.rules.Rule rule = underTest.findById(ruleDefinition.getId()); + verifyRule(rule, ruleDefinition, ruleParam); + } + } + + @Test + public void findById_returns_null_for_non_existing_id() { + assertThat(underTest.findById(new Random().nextInt())).isNull(); + } + + @Test + public void findByKey_returns_all_loaded_rules_by_id() { + for (int i = 0; i < ruleDefinitions.length; i++) { + RuleDefinitionDto ruleDefinition = ruleDefinitions[i]; + RuleParamDto ruleParam = ruleParams[i]; + + org.sonar.api.rules.Rule rule = underTest.findByKey(ruleDefinition.getKey()); + verifyRule(rule, ruleDefinition, ruleParam); + assertThat(underTest.findByKey(ruleDefinition.getRepositoryKey(), ruleDefinition.getRuleKey())) + .isSameAs(rule); + } + } + + @Test + public void findByKey_returns_null_when_RuleKey_is_null() { + assertThat(underTest.findByKey(null)).isNull(); + } + + @Test + public void findByKey_returns_null_when_repository_key_is_null() { + assertThat(underTest.findByKey(null, randomAlphabetic(2))).isNull(); + } + + @Test + public void findByKey_returns_null_when_key_is_null() { + assertThat(underTest.findByKey(randomAlphabetic(2), null)).isNull(); + } + + @Test + public void findByKey_returns_null_when_both_repository_key_and_key_are_null() { + assertThat(underTest.findByKey(null, null)).isNull(); + } + + @Test + public void find_returns_null_when_RuleQuery_is_empty() { + assertThat(underTest.find(null)).isNull(); + } + + @Test + public void find_returns_most_recent_rule_when_RuleQuery_has_no_non_null_field() { + Rule rule = underTest.find(RuleQuery.create()); + + assertThat(toRuleKey(rule)).isEqualTo(ruleDefinitions[5].getKey()); + } + + @Test + public void find_searches_by_exact_match_of_repository_key_and_returns_most_recent_rule() { + String repoKey = "ABCD"; + RuleDefinitionDto[] sameRepoKey = { + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())) + }; + RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient); + + assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(repoKey)))) + .isEqualTo(sameRepoKey[1].getKey()); + assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey())))) + .isEqualTo(otherRule.getKey()); + assertThat(underTest.find(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase()))) + .isNull(); + assertThat(underTest.find(RuleQuery.create().withRepositoryKey(randomAlphabetic(3)))) + .isNull(); + } + + @Test + public void find_searches_by_exact_match_of_ruleKey_and_returns_most_recent_rule() { + String ruleKey = "ABCD"; + RuleDefinitionDto[] sameRuleKey = { + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())) + }; + RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient); + + assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(ruleKey)))) + .isEqualTo(sameRuleKey[1].getKey()); + assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(otherRule.getRuleKey())))) + .isEqualTo(otherRule.getKey()); + assertThat(underTest.find(RuleQuery.create().withKey(ruleKey.toLowerCase()))) + .isNull(); + assertThat(underTest.find(RuleQuery.create().withKey(randomAlphabetic(3)))) + .isNull(); + } + + @Test + public void find_searches_by_exact_match_of_configKey_and_returns_most_recent_rule() { + String configKey = "ABCD"; + RuleDefinitionDto[] sameConfigKey = { + dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())) + }; + RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient); + + assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(configKey)))) + .isEqualTo(sameConfigKey[1].getKey()); + assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(otherRule.getConfigKey())))) + .isEqualTo(otherRule.getKey()); + assertThat(underTest.find(RuleQuery.create().withConfigKey(configKey.toLowerCase()))) + .isNull(); + assertThat(underTest.find(RuleQuery.create().withConfigKey(randomAlphabetic(3)))) + .isNull(); + } + + @Test + public void find_searches_by_exact_match_and_match_on_all_criterias_and_returns_most_recent_match() { + String repoKey = "ABCD"; + String ruleKey = "EFGH"; + String configKey = "IJKL"; + RuleDefinitionDto[] rules = { + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())) + }; + RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey); + RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey); + RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey); + RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey); + RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey); + RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey); + RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient); + + assertThat(toRuleKey(underTest.find(allQuery))).isEqualTo(rules[0].getKey()); + assertThat(toRuleKey(underTest.find(ruleAndConfigKeyQuery))).isEqualTo(rules[1].getKey()); + assertThat(toRuleKey(underTest.find(repoAndConfigKeyQuery))).isEqualTo(rules[2].getKey()); + assertThat(toRuleKey(underTest.find(repoAndKeyQuery))).isEqualTo(rules[0].getKey()); + assertThat(toRuleKey(underTest.find(repoKeyQuery))).isEqualTo(rules[2].getKey()); + assertThat(toRuleKey(underTest.find(ruleKeyQuery))).isEqualTo(rules[1].getKey()); + assertThat(toRuleKey(underTest.find(configKeyQuery))).isEqualTo(rules[2].getKey()); + } + + @Test + public void findAll_returns_empty_when_RuleQuery_is_empty() { + assertThat(underTest.findAll(null)).isEmpty(); + } + + @Test + public void findAll_returns_all_rules_when_RuleQuery_has_no_non_null_field() { + assertThat(underTest.findAll(RuleQuery.create())) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsOnly(Arrays.stream(ruleDefinitions).map(RuleDefinitionDto::getKey).toArray(RuleKey[]::new)); + } + + @Test + public void findAll_returns_all_rules_with_exact_same_repository_key_and_order_them_most_recent_first() { + String repoKey = "ABCD"; + RuleDefinitionDto[] sameRepoKey = { + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())) + }; + RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient); + + assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey))) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(sameRepoKey[1].getKey(), sameRepoKey[0].getKey()); + assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey()))) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(otherRule.getKey()); + assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase()))) + .isEmpty(); + assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(randomAlphabetic(3)))) + .isEmpty(); + } + + @Test + public void findAll_returns_all_rules_with_exact_same_rulekey_and_order_them_most_recent_first() { + String ruleKey = "ABCD"; + RuleDefinitionDto[] sameRuleKey = { + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())) + }; + RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient); + + assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey))) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(sameRuleKey[1].getKey(), sameRuleKey[0].getKey()); + assertThat(underTest.findAll(RuleQuery.create().withKey(otherRule.getRuleKey()))) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(otherRule.getKey()); + assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey.toLowerCase()))) + .isEmpty(); + assertThat(underTest.findAll(RuleQuery.create().withKey(randomAlphabetic(3)))) + .isEmpty(); + } + + @Test + public void findAll_returns_all_rules_with_exact_same_configkey_and_order_them_most_recent_first() { + String configKey = "ABCD"; + RuleDefinitionDto[] sameConfigKey = { + dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())) + }; + RuleDefinitionDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient); + + assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey))) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(sameConfigKey[1].getKey(), sameConfigKey[0].getKey()); + assertThat(underTest.findAll(RuleQuery.create().withConfigKey(otherRule.getConfigKey()))) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(otherRule.getKey()); + assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey.toLowerCase()))) + .isEmpty(); + assertThat(underTest.findAll(RuleQuery.create().withConfigKey(randomAlphabetic(3)))) + .isEmpty(); + } + + @Test + public void findAll_returns_all_rules_which_match_exactly_all_criteria_and_order_then_by_most_recent_first() { + String repoKey = "ABCD"; + String ruleKey = "EFGH"; + String configKey = "IJKL"; + RuleDefinitionDto[] rules = { + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())) + }; + RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey); + RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey); + RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey); + RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey); + RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey); + RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey); + RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient); + + assertThat(underTest.findAll(allQuery)) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(rules[0].getKey()); + assertThat(underTest.findAll(ruleAndConfigKeyQuery)) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(rules[1].getKey(), rules[0].getKey()); + assertThat(underTest.findAll(repoAndConfigKeyQuery)) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(rules[2].getKey(), rules[0].getKey()); + assertThat(underTest.findAll(repoAndKeyQuery)) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(rules[0].getKey()); + assertThat(underTest.findAll(repoKeyQuery)) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(rules[2].getKey(), rules[0].getKey()); + assertThat(underTest.findAll(ruleKeyQuery)) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(rules[1].getKey(), rules[0].getKey()); + assertThat(underTest.findAll(configKeyQuery)) + .extracting(CachingRuleFinderTest::toRuleKey) + .containsExactly(rules[2].getKey(), rules[1].getKey(), rules[0].getKey()); + } + + private static RuleKey toRuleKey(Rule rule) { + return RuleKey.of(rule.getRepositoryKey(), rule.getKey()); + } + + private void verifyRule(Rule rule, RuleDefinitionDto ruleDefinition, RuleParamDto ruleParam) { + assertThat(rule).isNotNull(); + + assertThat(rule.getName()).isEqualTo(ruleDefinition.getName()); + assertThat(rule.getLanguage()).isEqualTo(ruleDefinition.getLanguage()); + assertThat(rule.getKey()).isEqualTo(ruleDefinition.getRuleKey()); + assertThat(rule.getConfigKey()).isEqualTo(ruleDefinition.getConfigKey()); + assertThat(rule.isTemplate()).isEqualTo(ruleDefinition.isTemplate()); + assertThat(rule.getCreatedAt().getTime()).isEqualTo(ruleDefinition.getCreatedAt()); + assertThat(rule.getUpdatedAt().getTime()).isEqualTo(ruleDefinition.getUpdatedAt()); + assertThat(rule.getRepositoryKey()).isEqualTo(ruleDefinition.getRepositoryKey()); + assertThat(rule.getSeverity().name()).isEqualTo(ruleDefinition.getSeverityString()); + assertThat(rule.getTags()).isEqualTo(ruleDefinition.getSystemTags().stream().toArray(String[]::new)); + assertThat(rule.getId()).isEqualTo(ruleDefinition.getId()); + assertThat(rule.getDescription()).isEqualTo(ruleDefinition.getDescription()); + + assertThat(rule.getParams()).hasSize(1); + org.sonar.api.rules.RuleParam param = rule.getParams().iterator().next(); + assertThat(param.getRule()).isSameAs(rule); + assertThat(param.getKey()).isEqualTo(ruleParam.getName()); + assertThat(param.getDescription()).isEqualTo(ruleParam.getDescription()); + assertThat(param.getType()).isEqualTo(ruleParam.getType()); + assertThat(param.getDefaultValue()).isEqualTo(ruleParam.getDefaultValue()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java index 47009c005b1..b021ccab6a4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java @@ -55,10 +55,12 @@ import org.sonar.server.rule.index.RuleIndexer; import org.sonar.server.rule.index.RuleQuery; import static com.google.common.collect.Sets.newHashSet; -import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.api.rule.Severity.BLOCKER; import static org.sonar.api.rule.Severity.INFO; @@ -83,6 +85,7 @@ public class RegisterRulesTest { public LogTester logTester = new LogTester(); private RuleActivator ruleActivator = mock(RuleActivator.class); + private WebServerRuleFinder webServerRuleFinder = mock(WebServerRuleFinder.class); private DbClient dbClient = dbTester.getDbClient(); private RuleIndexer ruleIndexer; private ActiveRuleIndexer activeRuleIndexer; @@ -179,7 +182,7 @@ public class RegisterRulesTest { public void delete_repositories_that_have_been_uninstalled() { RuleRepositoryDto repository = new RuleRepositoryDto("findbugs", "java", "Findbugs"); DbSession dbSession = dbTester.getSession(); - dbTester.getDbClient().ruleRepositoryDao().insert(dbSession, asList(repository)); + dbTester.getDbClient().ruleRepositoryDao().insert(dbSession, singletonList(repository)); dbSession.commit(); execute(new FakeRepositoryV1()); @@ -195,7 +198,6 @@ public class RegisterRulesTest { // user adds tags and sets markdown note OrganizationDto defaultOrganization = dbTester.getDefaultOrganization(); - String organizationUuid = defaultOrganization.getUuid(); RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1); rule1.setTags(newHashSet("usertag1", "usertag2")); rule1.setNoteData("user *note*"); @@ -260,7 +262,6 @@ public class RegisterRulesTest { }); OrganizationDto defaultOrganization = dbTester.getDefaultOrganization(); - String organizationUuid = defaultOrganization.getUuid(); RuleDto rule = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1); assertThat(rule.getSystemTags()).containsOnly("tag1"); @@ -360,7 +361,6 @@ public class RegisterRulesTest { when(system.now()).thenReturn(DATE2.getTime()); execute(); - String organizationUuid = dbTester.getDefaultOrganization().getUuid(); RuleDto rule = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1); assertThat(rule.getStatus()).isEqualTo(RuleStatus.REMOVED); assertThat(ruleIndex.search(new RuleQuery().setKey(RULE_KEY1.toString()), new SearchOptions()).getTotal()).isEqualTo(0); @@ -484,11 +484,14 @@ public class RegisterRulesTest { RuleDefinitionsLoader loader = new RuleDefinitionsLoader(mock(DeprecatedRulesDefinitionLoader.class), mock(CommonRuleDefinitionsImpl.class), defs); Languages languages = mock(Languages.class); when(languages.get("java")).thenReturn(mock(Language.class)); + reset(webServerRuleFinder); - RegisterRules task = new RegisterRules(loader, ruleActivator, dbClient, ruleIndexer, activeRuleIndexer, languages, system, organizationFlags); + RegisterRules task = new RegisterRules(loader, ruleActivator, dbClient, ruleIndexer, activeRuleIndexer, languages, system, organizationFlags, webServerRuleFinder); task.start(); // Execute a commit to refresh session state as the task is using its own session dbTester.getSession().commit(); + + verify(webServerRuleFinder).startCaching(); } private RuleParamDto getParam(List params, String key) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/WebServerRuleFinderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/WebServerRuleFinderImplTest.java new file mode 100644 index 00000000000..dd4f9570b9b --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/WebServerRuleFinderImplTest.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.rule; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.rules.RuleFinder; +import org.sonar.db.DbClient; +import org.sonar.db.rule.RuleDao; +import org.sonar.server.organization.TestDefaultOrganizationProvider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class WebServerRuleFinderImplTest { + + private DbClient dbClient = mock(DbClient.class); + private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.fromUuid("1111"); + private WebServerRuleFinderImpl underTest = new WebServerRuleFinderImpl(dbClient, defaultOrganizationProvider); + + @Before + public void setUp() throws Exception { + when(dbClient.ruleDao()).thenReturn(mock(RuleDao.class)); + } + + @Test + public void constructor_initializes_with_non_caching_delegate() { + assertThat(underTest.delegate).isInstanceOf(DefaultRuleFinder.class); + } + + @Test + public void startCaching_sets_caching_delegate() { + underTest.startCaching(); + + assertThat(underTest.delegate).isInstanceOf(CachingRuleFinder.class); + } + + @Test + public void stopCaching_restores_non_caching_delegate() { + RuleFinder nonCachingDelegate = underTest.delegate; + + underTest.startCaching(); + underTest.stopCaching(); + + assertThat(underTest.delegate).isSameAs(nonCachingDelegate); + } +} -- 2.39.5