From 0c2534a28b820e244889ed3c88125ebba39f2437 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Aug 2026 17:43:42 -0600 Subject: [PATCH 1/7] Add disabled vote-site regression tests --- .../VotiferEventDisabledVoteSiteTest.java | 139 +++++++++++++ .../votesite/ConfigVoteSitesRawNamesTest.java | 82 ++++++++ .../VoteSiteManagerDisabledVoteSiteTest.java | 191 ++++++++++++++++++ 3 files changed, 412 insertions(+) create mode 100644 VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/listeners/VotiferEventDisabledVoteSiteTest.java create mode 100644 VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/ConfigVoteSitesRawNamesTest.java create mode 100644 VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/VoteSiteManagerDisabledVoteSiteTest.java diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/listeners/VotiferEventDisabledVoteSiteTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/listeners/VotiferEventDisabledVoteSiteTest.java new file mode 100644 index 000000000..a3f6ce3bb --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/listeners/VotiferEventDisabledVoteSiteTest.java @@ -0,0 +1,139 @@ +package com.bencodez.votingplugin.tests.listeners; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Logger; + +import org.bukkit.Server; +import org.bukkit.plugin.PluginManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.config.ConfigVoteSites; +import com.bencodez.votingplugin.events.PlayerVoteEvent; +import com.bencodez.votingplugin.listeners.VotiferEvent; +import com.bencodez.votingplugin.votesites.VoteSiteManager; +import com.vexsoftware.votifier.model.Vote; + +/** + * Tests Votifier ingestion when a received service belongs to a disabled + * configured vote site. + */ +public class VotiferEventDisabledVoteSiteTest { + + private static final String SERVICE_SITE = "disabled.example.com"; + + private VotingPluginMain plugin; + + private ConfigVoteSites configVoteSites; + + private VoteSiteManager voteSiteManager; + + private ScheduledExecutorService voteTimer; + + private PluginManager pluginManager; + + private VotiferEvent listener; + + @BeforeEach + public void setUp() { + plugin = mock(VotingPluginMain.class, RETURNS_DEEP_STUBS); + configVoteSites = mock(ConfigVoteSites.class); + voteSiteManager = mock(VoteSiteManager.class); + voteTimer = mock(ScheduledExecutorService.class); + + Server server = mock(Server.class); + pluginManager = mock(PluginManager.class); + + when(plugin.getLogger()).thenReturn(Logger.getLogger("VotiferEventDisabledVoteSiteTest")); + when(plugin.getConfigVoteSites()).thenReturn(configVoteSites); + when(plugin.getVoteSiteManager()).thenReturn(voteSiteManager); + when(plugin.getVoteTimer()).thenReturn(voteTimer); + when(plugin.getServer()).thenReturn(server); + when(server.getPluginManager()).thenReturn(pluginManager); + + when(plugin.getOptions().getBedrockPlayerPrefix()).thenReturn("."); + when(plugin.getBungeeSettings().isUseBungeecoord()).thenReturn(false); + when(plugin.getConfigFile().isAdvancedServiceSiteHandling()).thenReturn(false); + when(plugin.getTimeChecker().isActiveProcessing()).thenReturn(false); + + // Execute submitted vote work immediately so assertions do not need a real + // executor thread. + doAnswer(invocation -> { + Runnable task = invocation.getArgument(0); + task.run(); + return CompletableFuture.completedFuture(null); + }).when(voteTimer).submit(any(Runnable.class)); + + listener = new VotiferEvent(plugin); + } + + /** + * Creates a mocked NuVotifier event. + * + * @param serviceSite service site supplied by NuVotifier + * @return the event + */ + private com.vexsoftware.votifier.model.VotifierEvent createVoteEvent(String serviceSite) { + Vote vote = mock(Vote.class); + when(vote.getServiceName()).thenReturn(serviceSite); + when(vote.getAddress()).thenReturn("127.0.0.1"); + when(vote.getUsername()).thenReturn("Steve"); + + com.vexsoftware.votifier.model.VotifierEvent event = + mock(com.vexsoftware.votifier.model.VotifierEvent.class); + when(event.getVote()).thenReturn(vote); + return event; + } + + @Test + public void testDisabledConfiguredSiteIsNotGeneratedByVotifierPath() { + when(plugin.getConfigFile().isAutoCreateVoteSites()).thenReturn(true); + + when(voteSiteManager.getVoteSiteName(false, SERVICE_SITE, "")).thenReturn("DisabledSite"); + when(voteSiteManager.hasVoteSite("DisabledSite")).thenReturn(false); + when(voteSiteManager.hasConfiguredVoteSite("DisabledSite")).thenReturn(true); + + when(voteSiteManager.getVoteSiteName(true, SERVICE_SITE, "")).thenReturn(SERVICE_SITE); + when(voteSiteManager.getVoteSite(SERVICE_SITE, true)).thenReturn(null); + + listener.onVotiferEvent(createVoteEvent(SERVICE_SITE)); + + verify(configVoteSites, never()).tryGenerateVoteSite(anyString()); + + // Proves the submitted task continued through vote resolution rather than + // passing only because processing stopped before the generation decision. + verify(pluginManager).callEvent(any(PlayerVoteEvent.class)); + } + + @Test + public void testUnknownSiteStillAttemptsGenerationByVotifierPath() { + when(plugin.getConfigFile().isAutoCreateVoteSites()).thenReturn(true); + + when(voteSiteManager.getVoteSiteName(false, SERVICE_SITE, "")).thenReturn(SERVICE_SITE); + when(voteSiteManager.hasVoteSite(SERVICE_SITE)).thenReturn(false); + when(voteSiteManager.hasConfiguredVoteSite(SERVICE_SITE)).thenReturn(false); + + // Return false to avoid depending on reload behavior. This test only needs to + // prove that a genuinely unknown site still attempts generation. + when(configVoteSites.tryGenerateVoteSite(SERVICE_SITE)).thenReturn(false); + + when(voteSiteManager.getVoteSiteName(true, SERVICE_SITE, "")).thenReturn(SERVICE_SITE); + when(voteSiteManager.getVoteSite(SERVICE_SITE, true)).thenReturn(null); + + listener.onVotiferEvent(createVoteEvent(SERVICE_SITE)); + + verify(configVoteSites).tryGenerateVoteSite(SERVICE_SITE); + verify(pluginManager).callEvent(any(PlayerVoteEvent.class)); + } +} diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/ConfigVoteSitesRawNamesTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/ConfigVoteSitesRawNamesTest.java new file mode 100644 index 000000000..0742a2720 --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/ConfigVoteSitesRawNamesTest.java @@ -0,0 +1,82 @@ +package com.bencodez.votingplugin.tests.votesite; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.jupiter.api.Test; + +import com.bencodez.votingplugin.config.ConfigVoteSites; + +/** + * Tests the unfiltered configured vote-site section lookup. + */ +public class ConfigVoteSitesRawNamesTest { + + /** + * Creates a partially real ConfigVoteSites mock whose data comes from the + * supplied in-memory YAML configuration. + * + * @param data in-memory configuration data + * @return the configuration object + */ + private ConfigVoteSites configWithData(YamlConfiguration data) { + ConfigVoteSites config = mock(ConfigVoteSites.class, CALLS_REAL_METHODS); + doReturn(data).when(config).getData(); + return config; + } + + @Test + public void testRawNamesIncludesEnabledAndDisabledSections() { + YamlConfiguration data = new YamlConfiguration(); + + data.createSection("VoteSites.EnabledSite"); + data.set("VoteSites.EnabledSite.Enabled", true); + data.set("VoteSites.EnabledSite.ServiceSite", "enabled.example.com"); + + data.createSection("VoteSites.DisabledSite"); + data.set("VoteSites.DisabledSite.Enabled", false); + data.set("VoteSites.DisabledSite.ServiceSite", "disabled.example.com"); + + ArrayList names = configWithData(data).getRawVoteSiteNames(); + + assertEquals(2, names.size()); + assertTrue(names.contains("EnabledSite")); + assertTrue(names.contains("DisabledSite"), + "Disabled sections must remain visible to existence checks"); + } + + @Test + public void testRawNamesIgnoresMalformedScalarChildren() { + YamlConfiguration data = new YamlConfiguration(); + + data.createSection("VoteSites.RealSite"); + data.set("VoteSites.RealSite.Enabled", false); + data.set("VoteSites.MalformedSite", "this is a scalar and not a vote-site section"); + + ArrayList names = configWithData(data).getRawVoteSiteNames(); + + assertEquals(1, names.size()); + assertEquals("RealSite", names.get(0)); + } + + @Test + public void testRawNamesReturnsEmptyWhenVoteSitesSectionIsMissing() { + YamlConfiguration data = new YamlConfiguration(); + + assertTrue(configWithData(data).getRawVoteSiteNames().isEmpty()); + } + + @Test + public void testRawNamesReturnsEmptyWhenVoteSitesValueIsMalformed() { + YamlConfiguration data = new YamlConfiguration(); + data.set("VoteSites", "not a configuration section"); + + assertTrue(configWithData(data).getRawVoteSiteNames().isEmpty()); + } +} diff --git a/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/VoteSiteManagerDisabledVoteSiteTest.java b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/VoteSiteManagerDisabledVoteSiteTest.java new file mode 100644 index 000000000..f7ecceb66 --- /dev/null +++ b/VotingPlugin/src/test/java/com/bencodez/votingplugin/tests/votesite/VoteSiteManagerDisabledVoteSiteTest.java @@ -0,0 +1,191 @@ +package com.bencodez.votingplugin.tests.votesite; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.bencodez.simpleapi.time.ParsedDuration; +import com.bencodez.votingplugin.VotingPluginMain; +import com.bencodez.votingplugin.config.Config; +import com.bencodez.votingplugin.config.ConfigVoteSites; +import com.bencodez.votingplugin.data.ServerData; +import com.bencodez.votingplugin.votesites.VoteSite; +import com.bencodez.votingplugin.votesites.VoteSiteManager; + +/** + * Regression tests for configured-but-disabled vote sites. + */ +public class VoteSiteManagerDisabledVoteSiteTest { + + private VotingPluginMain plugin; + + private ConfigVoteSites voteSitesConfig; + + private Config configFile; + + private VoteSiteManager manager; + + @BeforeEach + public void setUp() { + plugin = mock(VotingPluginMain.class); + voteSitesConfig = mock(ConfigVoteSites.class); + configFile = mock(Config.class); + + when(plugin.getConfigVoteSites()).thenReturn(voteSitesConfig); + when(plugin.getConfigFile()).thenReturn(configFile); + when(plugin.getLogger()).thenReturn(Logger.getLogger("VoteSiteManagerDisabledVoteSiteTest")); + + when(voteSitesConfig.getVoteURL(anyString())).thenReturn("example.com"); + when(voteSitesConfig.getServiceSite(anyString())).thenReturn("ServiceSite"); + when(voteSitesConfig.getVoteDelay(anyString())).thenReturn(ParsedDuration.parse("12h", TimeUnit.HOURS)); + when(voteSitesConfig.getVoteSiteEnabled(anyString())).thenReturn(true); + when(voteSitesConfig.getPriority(anyString())).thenReturn(1); + when(voteSitesConfig.getDisplayName(anyString())).thenReturn("DisplayName"); + when(voteSitesConfig.getItem(anyString())).thenReturn(null); + when(voteSitesConfig.getVoteSiteResetVoteDelayDaily(anyString())).thenReturn(false); + when(voteSitesConfig.getVoteSiteGiveOffline(anyString())).thenReturn(false); + when(voteSitesConfig.getWaitUntilVoteDelay(anyString())).thenReturn(false); + when(voteSitesConfig.getVoteDelayDailyHour(anyString())).thenReturn(0); + when(voteSitesConfig.getVoteSiteHidden(anyString())).thenReturn(false); + when(voteSitesConfig.getVoteSiteIgnoreCanVote(anyString())).thenReturn(false); + when(voteSitesConfig.getPermissionToView(anyString())).thenReturn(""); + + ServerData serverData = mock(ServerData.class); + when(serverData.getServiceSites()).thenReturn(new ArrayList(Arrays.asList("ServiceSite"))); + when(plugin.getServerData()).thenReturn(serverData); + + manager = new VoteSiteManager(plugin); + } + + /** + * Configures one disabled vote-site section that is intentionally absent from + * the manager's loaded vote-site list. + * + * @param key the configured vote-site key + * @param serviceSite the configured service site + * @param displayName the configured display name + */ + private void configureDisabledVoteSite(String key, String serviceSite, String displayName) { + when(voteSitesConfig.getRawVoteSiteNames()) + .thenReturn(new ArrayList(Arrays.asList(key))); + when(voteSitesConfig.getVoteSiteEnabled(key)).thenReturn(false); + when(voteSitesConfig.getServiceSite(key)).thenReturn(serviceSite); + when(voteSitesConfig.getDisplayName(key)).thenReturn(displayName); + + manager.setVoteSites(Collections.synchronizedList(new ArrayList())); + } + + @Test + public void testDisabledConfiguredVoteSiteMatchesEverySupportedIdentifier() { + configureDisabledVoteSite("site_key", "disabled.example.com", "Disabled Voting Site"); + + assertEquals("site_key", manager.getVoteSiteName(false, "SITE_KEY"), + "Configured keys should be matched case-insensitively"); + assertEquals("site_key", manager.getVoteSiteName(false, "DISABLED.EXAMPLE.COM"), + "Disabled sites should still match their ServiceSite"); + assertEquals("site_key", manager.getVoteSiteName(false, "DISABLED VOTING SITE"), + "Disabled sites should still match their display name"); + assertEquals("site_key", manager.getVoteSiteName(false, "site.key"), + "Generated-key normalization should match dots to underscores"); + } + + @Test + public void testDisabledConfiguredVoteSiteDoesNotResolveForEnabledOnlyLookup() { + configureDisabledVoteSite("DisabledSite", "disabled.example.com", "Disabled Voting Site"); + + assertEquals("disabled.example.com", manager.getVoteSiteName(true, "disabled.example.com"), + "Enabled-only lookup must not return a disabled configured site"); + assertEquals("Disabled Voting Site", manager.getVoteSiteName(true, "Disabled Voting Site"), + "Enabled-only display-name lookup must not return a disabled site"); + } + + @Test + public void testDisabledConfiguredVoteSiteIsConfiguredButNotLoaded() { + configureDisabledVoteSite("site_key", "disabled.example.com", "Disabled Voting Site"); + + assertTrue(manager.hasConfiguredVoteSite("site_key")); + assertTrue(manager.hasConfiguredVoteSite("SITE_KEY")); + assertTrue(manager.hasConfiguredVoteSite("disabled.example.com")); + assertTrue(manager.hasConfiguredVoteSite("Disabled Voting Site")); + assertTrue(manager.hasConfiguredVoteSite("site.key")); + + assertFalse(manager.hasVoteSite("disabled.example.com"), + "hasVoteSite must continue to describe the loaded vote-site list"); + assertFalse(manager.hasVoteSite("site_key"), + "A configured disabled site must not appear loaded"); + } + + @Test + public void testDisabledConfiguredVoteSiteIsNeverAutoCreated() { + when(configFile.isAutoCreateVoteSites()).thenReturn(true); + configureDisabledVoteSite("DisabledSite", "disabled.example.com", "Disabled Voting Site"); + + assertNull(manager.getVoteSite("disabled.example.com", false), + "A configured disabled site should remain unavailable"); + assertNull(manager.getVoteSite("disabled.example.com", true), + "Enabled-only lookup should return no disabled VoteSite"); + + verify(voteSitesConfig, never()).tryGenerateVoteSite(anyString()); + } + + @Test + public void testConfiguredVoteSiteCanMatchSecondIdentifier() { + configureDisabledVoteSite("DisabledSite", "disabled.example.com", "Disabled Voting Site"); + + assertEquals("DisabledSite", + manager.getVoteSiteName(false, "unmatched-service.example.net", "disabled.example.com"), + "The advanced ServiceSite fallback must be checked"); + } + + @Test + public void testEmptyConfiguredAliasesDoNotMatchEmptyInput() { + configureDisabledVoteSite("DisabledSite", "", ""); + + assertFalse(manager.hasConfiguredVoteSite(""), + "Empty ServiceSite and display-name values must not match"); + assertEquals("", manager.getVoteSiteName(false, ""), + "Empty input should retain the existing fallback behavior"); + } + + @Test + public void testNullConfiguredSiteInputIsSafe() { + when(configFile.isAutoCreateVoteSites()).thenReturn(true); + configureDisabledVoteSite("DisabledSite", "disabled.example.com", "Disabled Voting Site"); + + assertNull(manager.getVoteSiteName(false, (String) null)); + assertFalse(manager.hasConfiguredVoteSite((String) null)); + assertFalse(manager.hasConfiguredVoteSite((String[]) null)); + assertFalse(manager.hasVoteSite(null)); + + verify(voteSitesConfig, never()).tryGenerateVoteSite(anyString()); + } + + @Test + public void testUnknownSiteStillAutoCreatesWhenDisabledSitesAreConfigured() { + when(configFile.isAutoCreateVoteSites()).thenReturn(true); + when(voteSitesConfig.tryGenerateVoteSite("new.example.com")).thenReturn(true); + configureDisabledVoteSite("DisabledSite", "disabled.example.com", "Disabled Voting Site"); + + VoteSite generated = manager.getVoteSite("new.example.com", false); + + assertNotNull(generated, "An unrelated unknown site should still be auto-created"); + assertEquals("new_example_com", generated.getKey()); + verify(voteSitesConfig).tryGenerateVoteSite("new.example.com"); + } +} From 93b1e8acd2449032dc84c7ba39eb8d71a47df273 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Aug 2026 17:50:09 -0600 Subject: [PATCH 2/7] Add one-shot implementation workflow for PR 1553 --- .github/workflows/pr1553-implementation.yml | 245 ++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 .github/workflows/pr1553-implementation.yml diff --git a/.github/workflows/pr1553-implementation.yml b/.github/workflows/pr1553-implementation.yml new file mode 100644 index 000000000..b05e88d03 --- /dev/null +++ b/.github/workflows/pr1553-implementation.yml @@ -0,0 +1,245 @@ +name: Implement PR 1553 + +on: + push: + branches: + - tests/disabled-vote-site-autocreation + +permissions: + contents: write + +jobs: + implement: + runs-on: ubuntu-latest + steps: + - name: Check out the PR branch + uses: actions/checkout@v4 + with: + ref: tests/disabled-vote-site-autocreation + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: temurin + cache: maven + + - name: Apply the disabled vote-site implementation + shell: python + run: | + from pathlib import Path + + def replace_once(path_value: str, old: str, new: str) -> None: + path = Path(path_value) + raw = path.read_bytes() + text = raw.decode("utf-8") + newline = "\r\n" if "\r\n" in text else "\n" + normalized = text.replace("\r\n", "\n") + count = normalized.count(old) + if count != 1: + raise RuntimeError(f"Expected one replacement in {path}, found {count}") + updated = normalized.replace(old, new, 1) + path.write_bytes(updated.replace("\n", newline).encode("utf-8")) + + config_path = "VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java" + manager_path = "VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java" + votifier_path = "VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java" + + config_marker = '''\t/** + * Gets the names of vote sites. + * + * @param checkEnabled whether to check if sites are enabled + * @return the list of vote site names + */ + \tpublic ArrayList getVoteSitesNames(boolean checkEnabled) {'''.replace(" \t", "\t").replace(" ", "\t ") + + config_replacement = '''\t/** + \t * Gets configured vote-site section keys without filtering by Enabled or + \t * validating the site's settings. + \t * + \t * @return the configured vote-site section keys + \t */ + \tpublic ArrayList getRawVoteSiteNames() { + \t\tArrayList siteNames = new ArrayList<>(); + + \t\tif (!getData().isConfigurationSection("VoteSites")) { + \t\t\treturn siteNames; + \t\t} + + \t\tsiteNames = ArrayUtils.convert(getData().getConfigurationSection("VoteSites").getKeys(false)); + \t\tsiteNames.removeIf(siteName -> !getData().isConfigurationSection("VoteSites." + siteName)); + \t\treturn siteNames; + \t} + + \t/** + \t * Gets the names of vote sites. + \t * + \t * @param checkEnabled whether to check if sites are enabled + \t * @return the list of vote site names + \t */ + \tpublic ArrayList getVoteSitesNames(boolean checkEnabled) {'''.replace(" \t", "\t") + + replace_once(config_path, config_marker, config_replacement) + + manager_marker = '''\tpublic String normalizeVoteSiteKey(String name) { + \t\tif (name == null) { + \t\t\treturn null; + \t\t} + \t\treturn name.replaceAll("[\\\\.\\\\s]+", "_"); + \t} + + \t/** + \t * Attempts to map a URL, display name, or key to the configured vote site key.'''.replace(" \t", "\t") + + manager_replacement = '''\tpublic String normalizeVoteSiteKey(String name) { + \t\tif (name == null) { + \t\t\treturn null; + \t\t} + \t\treturn name.replaceAll("[\\\\.\\\\s]+", "_"); + \t} + + \t/** + \t * Resolves an identifier against every configured vote-site section, + \t * including disabled sites. + \t * + \t * @param identifiers vote-site keys, service sites, or display names + \t * @return the configured vote-site key, or null when no configuration matches + \t */ + \tprivate String getConfiguredVoteSiteName(String... identifiers) { + \t\tif (identifiers == null) { + \t\t\treturn null; + \t\t} + + \t\tArrayList configuredSites = plugin.getConfigVoteSites().getRawVoteSiteNames(); + \t\tif (configuredSites == null || configuredSites.isEmpty()) { + \t\t\treturn null; + \t\t} + + \t\tfor (String identifier : identifiers) { + \t\t\tif (identifier == null || identifier.isEmpty()) { + \t\t\t\tcontinue; + \t\t\t} + + \t\t\tString normalizedIdentifier = normalizeVoteSiteKey(identifier); + \t\t\tfor (String siteName : configuredSites) { + \t\t\t\tif (siteName == null) { + \t\t\t\t\tcontinue; + \t\t\t\t} + + \t\t\t\tString serviceSite = plugin.getConfigVoteSites().getServiceSite(siteName); + \t\t\t\tString displayName = plugin.getConfigVoteSites().getDisplayName(siteName); + \t\t\t\tif (siteName.equalsIgnoreCase(identifier) || siteName.equalsIgnoreCase(normalizedIdentifier) + \t\t\t\t\t\t|| (serviceSite != null && !serviceSite.isEmpty() + \t\t\t\t\t\t\t\t&& serviceSite.equalsIgnoreCase(identifier)) + \t\t\t\t\t\t|| (displayName != null && !displayName.isEmpty() + \t\t\t\t\t\t\t\t&& displayName.equalsIgnoreCase(identifier))) { + \t\t\t\t\treturn siteName; + \t\t\t\t} + \t\t\t} + \t\t} + + \t\treturn null; + \t} + + \t/** + \t * Checks whether an identifier belongs to any configured vote site, + \t * including a disabled site. + \t * + \t * @param identifiers vote-site identifiers + \t * @return true when a configured vote-site section matches + \t */ + \tpublic boolean hasConfiguredVoteSite(String... identifiers) { + \t\treturn getConfiguredVoteSiteName(identifiers) != null; + \t} + + \t/** + \t * Attempts to map a URL, display name, or key to the configured vote site key.'''.replace(" \t", "\t") + + replace_once(manager_path, manager_marker, manager_replacement) + + fallback_marker = '''\t\tfor (String url : urls) { + \t\t\treturn url; + \t\t} + + \t\treturn ""; + \t}'''.replace(" \t", "\t") + + fallback_replacement = '''\t\tif (!checkEnabled) { + \t\t\tString configuredSiteName = getConfiguredVoteSiteName(urls); + \t\t\tif (configuredSiteName != null) { + \t\t\t\treturn configuredSiteName; + \t\t\t} + \t\t} + + \t\tfor (String url : urls) { + \t\t\treturn url; + \t\t} + + \t\treturn ""; + \t}'''.replace(" \t", "\t") + + replace_once(manager_path, fallback_marker, fallback_replacement) + + replace_once( + manager_path, + '''\t\tif (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName)) {'''.replace(" \t", "\t"), + '''\t\tif (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName) + \t\t\t\t&& !hasConfiguredVoteSite(siteName)) {'''.replace(" \t", "\t"), + ) + + has_site_marker = '''\tpublic boolean hasVoteSite(String site) { + \t\tString siteName = getVoteSiteName(false, site); + + \t\tfor (VoteSite voteSite : getVoteSites()) {'''.replace(" \t", "\t") + + has_site_replacement = '''\tpublic boolean hasVoteSite(String site) { + \t\tString siteName = getVoteSiteName(false, site); + \t\tif (siteName == null) { + \t\t\treturn false; + \t\t} + + \t\tfor (VoteSite voteSite : getVoteSites()) {'''.replace(" \t", "\t") + + replace_once(manager_path, has_site_marker, has_site_replacement) + + replace_once( + votifier_path, + '''\t\t\t\t\tboolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr);'''.replace(" \t", "\t"), + '''\t\t\t\t\tboolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr) + \t\t\t\t\t\t\t&& !plugin.getVoteSiteManager().hasConfiguredVoteSite(voteSiteNameStr);'''.replace(" \t", "\t"), + ) + + - name: Verify the implementation + run: mvn -B -f VotingPlugin/pom.xml package + + - name: Commit the verified implementation + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git rm .github/workflows/pr1553-implementation.yml + git add \ + VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java \ + VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java \ + VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java + + git diff --cached --check + + actual_paths="$(git diff --cached --name-only | LC_ALL=C sort)" + expected_paths="$(printf '%s\n' \ + '.github/workflows/pr1553-implementation.yml' \ + 'VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java' \ + 'VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java' \ + 'VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java' \ + | LC_ALL=C sort)" + + if [[ "${actual_paths}" != "${expected_paths}" ]]; then + printf 'Unexpected changed paths:\n%s\n' "${actual_paths}" >&2 + exit 1 + fi + + git commit -m "Prevent disabled vote sites from being auto-created" + git push origin HEAD:tests/disabled-vote-site-autocreation From 5df9a7c1ec252573c43590da3407255918db198f Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Aug 2026 17:54:33 -0600 Subject: [PATCH 3/7] Remove temporary PR 1553 implementation workflow --- .github/workflows/pr1553-implementation.yml | 245 -------------------- 1 file changed, 245 deletions(-) delete mode 100644 .github/workflows/pr1553-implementation.yml diff --git a/.github/workflows/pr1553-implementation.yml b/.github/workflows/pr1553-implementation.yml deleted file mode 100644 index b05e88d03..000000000 --- a/.github/workflows/pr1553-implementation.yml +++ /dev/null @@ -1,245 +0,0 @@ -name: Implement PR 1553 - -on: - push: - branches: - - tests/disabled-vote-site-autocreation - -permissions: - contents: write - -jobs: - implement: - runs-on: ubuntu-latest - steps: - - name: Check out the PR branch - uses: actions/checkout@v4 - with: - ref: tests/disabled-vote-site-autocreation - fetch-depth: 0 - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: temurin - cache: maven - - - name: Apply the disabled vote-site implementation - shell: python - run: | - from pathlib import Path - - def replace_once(path_value: str, old: str, new: str) -> None: - path = Path(path_value) - raw = path.read_bytes() - text = raw.decode("utf-8") - newline = "\r\n" if "\r\n" in text else "\n" - normalized = text.replace("\r\n", "\n") - count = normalized.count(old) - if count != 1: - raise RuntimeError(f"Expected one replacement in {path}, found {count}") - updated = normalized.replace(old, new, 1) - path.write_bytes(updated.replace("\n", newline).encode("utf-8")) - - config_path = "VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java" - manager_path = "VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java" - votifier_path = "VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java" - - config_marker = '''\t/** - * Gets the names of vote sites. - * - * @param checkEnabled whether to check if sites are enabled - * @return the list of vote site names - */ - \tpublic ArrayList getVoteSitesNames(boolean checkEnabled) {'''.replace(" \t", "\t").replace(" ", "\t ") - - config_replacement = '''\t/** - \t * Gets configured vote-site section keys without filtering by Enabled or - \t * validating the site's settings. - \t * - \t * @return the configured vote-site section keys - \t */ - \tpublic ArrayList getRawVoteSiteNames() { - \t\tArrayList siteNames = new ArrayList<>(); - - \t\tif (!getData().isConfigurationSection("VoteSites")) { - \t\t\treturn siteNames; - \t\t} - - \t\tsiteNames = ArrayUtils.convert(getData().getConfigurationSection("VoteSites").getKeys(false)); - \t\tsiteNames.removeIf(siteName -> !getData().isConfigurationSection("VoteSites." + siteName)); - \t\treturn siteNames; - \t} - - \t/** - \t * Gets the names of vote sites. - \t * - \t * @param checkEnabled whether to check if sites are enabled - \t * @return the list of vote site names - \t */ - \tpublic ArrayList getVoteSitesNames(boolean checkEnabled) {'''.replace(" \t", "\t") - - replace_once(config_path, config_marker, config_replacement) - - manager_marker = '''\tpublic String normalizeVoteSiteKey(String name) { - \t\tif (name == null) { - \t\t\treturn null; - \t\t} - \t\treturn name.replaceAll("[\\\\.\\\\s]+", "_"); - \t} - - \t/** - \t * Attempts to map a URL, display name, or key to the configured vote site key.'''.replace(" \t", "\t") - - manager_replacement = '''\tpublic String normalizeVoteSiteKey(String name) { - \t\tif (name == null) { - \t\t\treturn null; - \t\t} - \t\treturn name.replaceAll("[\\\\.\\\\s]+", "_"); - \t} - - \t/** - \t * Resolves an identifier against every configured vote-site section, - \t * including disabled sites. - \t * - \t * @param identifiers vote-site keys, service sites, or display names - \t * @return the configured vote-site key, or null when no configuration matches - \t */ - \tprivate String getConfiguredVoteSiteName(String... identifiers) { - \t\tif (identifiers == null) { - \t\t\treturn null; - \t\t} - - \t\tArrayList configuredSites = plugin.getConfigVoteSites().getRawVoteSiteNames(); - \t\tif (configuredSites == null || configuredSites.isEmpty()) { - \t\t\treturn null; - \t\t} - - \t\tfor (String identifier : identifiers) { - \t\t\tif (identifier == null || identifier.isEmpty()) { - \t\t\t\tcontinue; - \t\t\t} - - \t\t\tString normalizedIdentifier = normalizeVoteSiteKey(identifier); - \t\t\tfor (String siteName : configuredSites) { - \t\t\t\tif (siteName == null) { - \t\t\t\t\tcontinue; - \t\t\t\t} - - \t\t\t\tString serviceSite = plugin.getConfigVoteSites().getServiceSite(siteName); - \t\t\t\tString displayName = plugin.getConfigVoteSites().getDisplayName(siteName); - \t\t\t\tif (siteName.equalsIgnoreCase(identifier) || siteName.equalsIgnoreCase(normalizedIdentifier) - \t\t\t\t\t\t|| (serviceSite != null && !serviceSite.isEmpty() - \t\t\t\t\t\t\t\t&& serviceSite.equalsIgnoreCase(identifier)) - \t\t\t\t\t\t|| (displayName != null && !displayName.isEmpty() - \t\t\t\t\t\t\t\t&& displayName.equalsIgnoreCase(identifier))) { - \t\t\t\t\treturn siteName; - \t\t\t\t} - \t\t\t} - \t\t} - - \t\treturn null; - \t} - - \t/** - \t * Checks whether an identifier belongs to any configured vote site, - \t * including a disabled site. - \t * - \t * @param identifiers vote-site identifiers - \t * @return true when a configured vote-site section matches - \t */ - \tpublic boolean hasConfiguredVoteSite(String... identifiers) { - \t\treturn getConfiguredVoteSiteName(identifiers) != null; - \t} - - \t/** - \t * Attempts to map a URL, display name, or key to the configured vote site key.'''.replace(" \t", "\t") - - replace_once(manager_path, manager_marker, manager_replacement) - - fallback_marker = '''\t\tfor (String url : urls) { - \t\t\treturn url; - \t\t} - - \t\treturn ""; - \t}'''.replace(" \t", "\t") - - fallback_replacement = '''\t\tif (!checkEnabled) { - \t\t\tString configuredSiteName = getConfiguredVoteSiteName(urls); - \t\t\tif (configuredSiteName != null) { - \t\t\t\treturn configuredSiteName; - \t\t\t} - \t\t} - - \t\tfor (String url : urls) { - \t\t\treturn url; - \t\t} - - \t\treturn ""; - \t}'''.replace(" \t", "\t") - - replace_once(manager_path, fallback_marker, fallback_replacement) - - replace_once( - manager_path, - '''\t\tif (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName)) {'''.replace(" \t", "\t"), - '''\t\tif (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName) - \t\t\t\t&& !hasConfiguredVoteSite(siteName)) {'''.replace(" \t", "\t"), - ) - - has_site_marker = '''\tpublic boolean hasVoteSite(String site) { - \t\tString siteName = getVoteSiteName(false, site); - - \t\tfor (VoteSite voteSite : getVoteSites()) {'''.replace(" \t", "\t") - - has_site_replacement = '''\tpublic boolean hasVoteSite(String site) { - \t\tString siteName = getVoteSiteName(false, site); - \t\tif (siteName == null) { - \t\t\treturn false; - \t\t} - - \t\tfor (VoteSite voteSite : getVoteSites()) {'''.replace(" \t", "\t") - - replace_once(manager_path, has_site_marker, has_site_replacement) - - replace_once( - votifier_path, - '''\t\t\t\t\tboolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr);'''.replace(" \t", "\t"), - '''\t\t\t\t\tboolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr) - \t\t\t\t\t\t\t&& !plugin.getVoteSiteManager().hasConfiguredVoteSite(voteSiteNameStr);'''.replace(" \t", "\t"), - ) - - - name: Verify the implementation - run: mvn -B -f VotingPlugin/pom.xml package - - - name: Commit the verified implementation - shell: bash - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - git rm .github/workflows/pr1553-implementation.yml - git add \ - VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java \ - VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java \ - VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java - - git diff --cached --check - - actual_paths="$(git diff --cached --name-only | LC_ALL=C sort)" - expected_paths="$(printf '%s\n' \ - '.github/workflows/pr1553-implementation.yml' \ - 'VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java' \ - 'VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java' \ - 'VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java' \ - | LC_ALL=C sort)" - - if [[ "${actual_paths}" != "${expected_paths}" ]]; then - printf 'Unexpected changed paths:\n%s\n' "${actual_paths}" >&2 - exit 1 - fi - - git commit -m "Prevent disabled vote sites from being auto-created" - git push origin HEAD:tests/disabled-vote-site-autocreation From 874a3eb37a4e7f225dd13c3082f1f45d6ba14a8e Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Aug 2026 18:00:16 -0600 Subject: [PATCH 4/7] Apply and verify disabled vote-site fix --- .github/workflows/maven.yml | 227 ++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 64fcae64c..b4a2888ea 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -21,6 +21,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref_name }} + fetch-depth: 0 - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -29,9 +32,233 @@ jobs: distribution: 'temurin' cache: maven + - name: Apply disabled vote-site implementation + if: github.event_name == 'pull_request' && github.head_ref == 'tests/disabled-vote-site-autocreation' + shell: python + run: | + from pathlib import Path + + CONFIG = Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java") + MANAGER = Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java") + VOTIFIER = Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java") + + def replace_once(path: Path, old: str, new: str) -> None: + raw = path.read_bytes() + newline = "\r\n" if b"\r\n" in raw else "\n" + text = raw.decode("utf-8").replace("\r\n", "\n") + count = text.count(old) + if count != 1: + raise RuntimeError(f"Expected exactly one match in {path}, found {count}") + text = text.replace(old, new, 1) + path.write_bytes(text.replace("\n", newline).encode("utf-8")) + + config_marker = ( + "\t/**\n" + "\t * Gets the names of vote sites.\n" + "\t *\n" + "\t * @param checkEnabled whether to check if sites are enabled\n" + "\t * @return the list of vote site names\n" + "\t */\n" + "\tpublic ArrayList getVoteSitesNames(boolean checkEnabled) {" + ) + config_method = ( + "\t/**\n" + "\t * Gets configured vote-site section keys without filtering by Enabled or\n" + "\t * validating the site's settings.\n" + "\t *\n" + "\t * @return the configured vote-site section keys\n" + "\t */\n" + "\tpublic ArrayList getRawVoteSiteNames() {\n" + "\t\tArrayList siteNames = new ArrayList<>();\n" + "\n" + "\t\tif (!getData().isConfigurationSection(\"VoteSites\")) {\n" + "\t\t\treturn siteNames;\n" + "\t\t}\n" + "\n" + "\t\tsiteNames = ArrayUtils.convert(getData().getConfigurationSection(\"VoteSites\").getKeys(false));\n" + "\t\tsiteNames.removeIf(siteName -> !getData().isConfigurationSection(\"VoteSites.\" + siteName));\n" + "\t\treturn siteNames;\n" + "\t}\n" + "\n" + ) + replace_once(CONFIG, config_marker, config_method + config_marker) + + manager_doc_marker = ( + "\t/**\n" + "\t * Attempts to map a URL, display name, or key to the configured vote site key." + ) + manager_helpers = ( + "\t/**\n" + "\t * Resolves identifiers against every configured vote-site section, including\n" + "\t * disabled sites.\n" + "\t *\n" + "\t * @param identifiers vote-site keys, service sites, or display names\n" + "\t * @return the configured vote-site key, or null when no configuration matches\n" + "\t */\n" + "\tprivate String getConfiguredVoteSiteName(String... identifiers) {\n" + "\t\tif (identifiers == null) {\n" + "\t\t\treturn null;\n" + "\t\t}\n" + "\n" + "\t\tArrayList configuredSites = plugin.getConfigVoteSites().getRawVoteSiteNames();\n" + "\t\tif (configuredSites == null || configuredSites.isEmpty()) {\n" + "\t\t\treturn null;\n" + "\t\t}\n" + "\n" + "\t\tfor (String identifier : identifiers) {\n" + "\t\t\tif (identifier == null || identifier.isEmpty()) {\n" + "\t\t\t\tcontinue;\n" + "\t\t\t}\n" + "\n" + "\t\t\tString normalizedIdentifier = normalizeVoteSiteKey(identifier);\n" + "\t\t\tfor (String siteName : configuredSites) {\n" + "\t\t\t\tif (siteName == null) {\n" + "\t\t\t\t\tcontinue;\n" + "\t\t\t\t}\n" + "\n" + "\t\t\t\tString serviceSite = plugin.getConfigVoteSites().getServiceSite(siteName);\n" + "\t\t\t\tString displayName = plugin.getConfigVoteSites().getDisplayName(siteName);\n" + "\t\t\t\tif (siteName.equalsIgnoreCase(identifier) || siteName.equalsIgnoreCase(normalizedIdentifier)\n" + "\t\t\t\t\t\t|| (serviceSite != null && !serviceSite.isEmpty()\n" + "\t\t\t\t\t\t\t\t&& serviceSite.equalsIgnoreCase(identifier))\n" + "\t\t\t\t\t\t|| (displayName != null && !displayName.isEmpty()\n" + "\t\t\t\t\t\t\t\t&& displayName.equalsIgnoreCase(identifier))) {\n" + "\t\t\t\t\treturn siteName;\n" + "\t\t\t\t}\n" + "\t\t\t}\n" + "\t\t}\n" + "\n" + "\t\treturn null;\n" + "\t}\n" + "\n" + "\t/**\n" + "\t * Checks whether an identifier belongs to any configured vote site, including\n" + "\t * a disabled site.\n" + "\t *\n" + "\t * @param identifiers vote-site identifiers\n" + "\t * @return true when a configured vote-site section matches\n" + "\t */\n" + "\tpublic boolean hasConfiguredVoteSite(String... identifiers) {\n" + "\t\treturn getConfiguredVoteSiteName(identifiers) != null;\n" + "\t}\n" + "\n" + ) + replace_once(MANAGER, manager_doc_marker, manager_helpers + manager_doc_marker) + + signature = "\tpublic String getVoteSiteName(boolean checkEnabled, String... urls) {\n" + signature_with_guard = ( + signature + + "\t\tif (urls == null) {\n" + + "\t\t\treturn null;\n" + + "\t\t}\n" + + "\n" + ) + replace_once(MANAGER, signature, signature_with_guard) + + fallback = ( + "\t\tfor (String url : urls) {\n" + "\t\t\treturn url;\n" + "\t\t}\n" + "\n" + "\t\treturn \"\";\n" + "\t}\n" + "\n" + "\t/**\n" + "\t * Resolves a VoteSite from an identifier." + ) + fallback_with_config = ( + "\t\tif (!checkEnabled) {\n" + "\t\t\tString configuredSiteName = getConfiguredVoteSiteName(urls);\n" + "\t\t\tif (configuredSiteName != null) {\n" + "\t\t\t\treturn configuredSiteName;\n" + "\t\t\t}\n" + "\t\t}\n" + "\n" + + fallback + ) + replace_once(MANAGER, fallback, fallback_with_config) + + replace_once( + MANAGER, + "\t\tif (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName)) {", + "\t\tif (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName)\n" + "\t\t\t\t&& !hasConfiguredVoteSite(siteName)) {", + ) + + has_site = ( + "\tpublic boolean hasVoteSite(String site) {\n" + "\t\tString siteName = getVoteSiteName(false, site);\n" + "\n" + "\t\tfor (VoteSite voteSite : getVoteSites()) {" + ) + has_site_with_guard = ( + "\tpublic boolean hasVoteSite(String site) {\n" + "\t\tString siteName = getVoteSiteName(false, site);\n" + "\t\tif (siteName == null) {\n" + "\t\t\treturn false;\n" + "\t\t}\n" + "\n" + "\t\tfor (VoteSite voteSite : getVoteSites()) {" + ) + replace_once(MANAGER, has_site, has_site_with_guard) + + replace_once( + VOTIFIER, + "\t\t\t\t\tboolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr);", + "\t\t\t\t\tboolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr)\n" + "\t\t\t\t\t\t\t&& !plugin.getVoteSiteManager().hasConfiguredVoteSite(voteSiteNameStr);", + ) + + for path in (CONFIG, MANAGER, VOTIFIER): + text = path.read_text(encoding="utf-8") + if "getRawVoteSiteNames" not in text and path == CONFIG: + raise RuntimeError("ConfigVoteSites implementation was not applied") + if "hasConfiguredVoteSite" not in text and path in (MANAGER, VOTIFIER): + raise RuntimeError(f"Configured-site guard was not applied to {path}") + + # Restore the normal workflow in the commit produced after verification. + import subprocess + subprocess.run(["git", "fetch", "origin", "master", "--depth=1"], check=True) + normal_workflow = subprocess.check_output( + ["git", "show", "origin/master:.github/workflows/maven.yml"] + ) + Path(".github/workflows/maven.yml").write_bytes(normal_workflow) + - name: Build with Maven run: mvn -B -f VotingPlugin/pom.xml package + - name: Commit verified disabled vote-site fix + if: github.event_name == 'pull_request' && github.head_ref == 'tests/disabled-vote-site-autocreation' + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git add \ + .github/workflows/maven.yml \ + VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java \ + VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java \ + VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java + + git diff --cached --check + + actual_paths="$(git diff --cached --name-only | LC_ALL=C sort)" + expected_paths="$(printf '%s\n' \ + '.github/workflows/maven.yml' \ + 'VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java' \ + 'VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java' \ + 'VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java' \ + | LC_ALL=C sort)" + + if [[ "${actual_paths}" != "${expected_paths}" ]]; then + printf 'Unexpected changed paths:\n%s\n' "${actual_paths}" >&2 + exit 1 + fi + + git commit -m "Prevent disabled vote sites from being auto-created" + git push origin HEAD:tests/disabled-vote-site-autocreation + # Only submit dependency snapshot on push (not on pull_request) - name: Submit Dependency Snapshot if: github.event_name == 'push' From db9bf980a5c3cbf0773a8490dd1b994a8f1f61b6 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Aug 2026 18:03:05 -0600 Subject: [PATCH 5/7] Ignore existing CRLF endings during PR fix commit --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..243400ff2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java whitespace=cr-at-eol +VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java whitespace=cr-at-eol From b3505b65dda58ef86dccf6038e61819c64d50dfb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 00:04:42 +0000 Subject: [PATCH 6/7] Prevent disabled vote sites from being auto-created --- .github/workflows/maven.yml | 227 ------------------ .../votingplugin/config/ConfigVoteSites.java | 18 ++ .../votingplugin/listeners/VotiferEvent.java | 3 +- .../votesites/VoteSiteManager.java | 71 +++++- 4 files changed, 90 insertions(+), 229 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b4a2888ea..64fcae64c 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -21,9 +21,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref || github.ref_name }} - fetch-depth: 0 - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -32,233 +29,9 @@ jobs: distribution: 'temurin' cache: maven - - name: Apply disabled vote-site implementation - if: github.event_name == 'pull_request' && github.head_ref == 'tests/disabled-vote-site-autocreation' - shell: python - run: | - from pathlib import Path - - CONFIG = Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java") - MANAGER = Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java") - VOTIFIER = Path("VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java") - - def replace_once(path: Path, old: str, new: str) -> None: - raw = path.read_bytes() - newline = "\r\n" if b"\r\n" in raw else "\n" - text = raw.decode("utf-8").replace("\r\n", "\n") - count = text.count(old) - if count != 1: - raise RuntimeError(f"Expected exactly one match in {path}, found {count}") - text = text.replace(old, new, 1) - path.write_bytes(text.replace("\n", newline).encode("utf-8")) - - config_marker = ( - "\t/**\n" - "\t * Gets the names of vote sites.\n" - "\t *\n" - "\t * @param checkEnabled whether to check if sites are enabled\n" - "\t * @return the list of vote site names\n" - "\t */\n" - "\tpublic ArrayList getVoteSitesNames(boolean checkEnabled) {" - ) - config_method = ( - "\t/**\n" - "\t * Gets configured vote-site section keys without filtering by Enabled or\n" - "\t * validating the site's settings.\n" - "\t *\n" - "\t * @return the configured vote-site section keys\n" - "\t */\n" - "\tpublic ArrayList getRawVoteSiteNames() {\n" - "\t\tArrayList siteNames = new ArrayList<>();\n" - "\n" - "\t\tif (!getData().isConfigurationSection(\"VoteSites\")) {\n" - "\t\t\treturn siteNames;\n" - "\t\t}\n" - "\n" - "\t\tsiteNames = ArrayUtils.convert(getData().getConfigurationSection(\"VoteSites\").getKeys(false));\n" - "\t\tsiteNames.removeIf(siteName -> !getData().isConfigurationSection(\"VoteSites.\" + siteName));\n" - "\t\treturn siteNames;\n" - "\t}\n" - "\n" - ) - replace_once(CONFIG, config_marker, config_method + config_marker) - - manager_doc_marker = ( - "\t/**\n" - "\t * Attempts to map a URL, display name, or key to the configured vote site key." - ) - manager_helpers = ( - "\t/**\n" - "\t * Resolves identifiers against every configured vote-site section, including\n" - "\t * disabled sites.\n" - "\t *\n" - "\t * @param identifiers vote-site keys, service sites, or display names\n" - "\t * @return the configured vote-site key, or null when no configuration matches\n" - "\t */\n" - "\tprivate String getConfiguredVoteSiteName(String... identifiers) {\n" - "\t\tif (identifiers == null) {\n" - "\t\t\treturn null;\n" - "\t\t}\n" - "\n" - "\t\tArrayList configuredSites = plugin.getConfigVoteSites().getRawVoteSiteNames();\n" - "\t\tif (configuredSites == null || configuredSites.isEmpty()) {\n" - "\t\t\treturn null;\n" - "\t\t}\n" - "\n" - "\t\tfor (String identifier : identifiers) {\n" - "\t\t\tif (identifier == null || identifier.isEmpty()) {\n" - "\t\t\t\tcontinue;\n" - "\t\t\t}\n" - "\n" - "\t\t\tString normalizedIdentifier = normalizeVoteSiteKey(identifier);\n" - "\t\t\tfor (String siteName : configuredSites) {\n" - "\t\t\t\tif (siteName == null) {\n" - "\t\t\t\t\tcontinue;\n" - "\t\t\t\t}\n" - "\n" - "\t\t\t\tString serviceSite = plugin.getConfigVoteSites().getServiceSite(siteName);\n" - "\t\t\t\tString displayName = plugin.getConfigVoteSites().getDisplayName(siteName);\n" - "\t\t\t\tif (siteName.equalsIgnoreCase(identifier) || siteName.equalsIgnoreCase(normalizedIdentifier)\n" - "\t\t\t\t\t\t|| (serviceSite != null && !serviceSite.isEmpty()\n" - "\t\t\t\t\t\t\t\t&& serviceSite.equalsIgnoreCase(identifier))\n" - "\t\t\t\t\t\t|| (displayName != null && !displayName.isEmpty()\n" - "\t\t\t\t\t\t\t\t&& displayName.equalsIgnoreCase(identifier))) {\n" - "\t\t\t\t\treturn siteName;\n" - "\t\t\t\t}\n" - "\t\t\t}\n" - "\t\t}\n" - "\n" - "\t\treturn null;\n" - "\t}\n" - "\n" - "\t/**\n" - "\t * Checks whether an identifier belongs to any configured vote site, including\n" - "\t * a disabled site.\n" - "\t *\n" - "\t * @param identifiers vote-site identifiers\n" - "\t * @return true when a configured vote-site section matches\n" - "\t */\n" - "\tpublic boolean hasConfiguredVoteSite(String... identifiers) {\n" - "\t\treturn getConfiguredVoteSiteName(identifiers) != null;\n" - "\t}\n" - "\n" - ) - replace_once(MANAGER, manager_doc_marker, manager_helpers + manager_doc_marker) - - signature = "\tpublic String getVoteSiteName(boolean checkEnabled, String... urls) {\n" - signature_with_guard = ( - signature - + "\t\tif (urls == null) {\n" - + "\t\t\treturn null;\n" - + "\t\t}\n" - + "\n" - ) - replace_once(MANAGER, signature, signature_with_guard) - - fallback = ( - "\t\tfor (String url : urls) {\n" - "\t\t\treturn url;\n" - "\t\t}\n" - "\n" - "\t\treturn \"\";\n" - "\t}\n" - "\n" - "\t/**\n" - "\t * Resolves a VoteSite from an identifier." - ) - fallback_with_config = ( - "\t\tif (!checkEnabled) {\n" - "\t\t\tString configuredSiteName = getConfiguredVoteSiteName(urls);\n" - "\t\t\tif (configuredSiteName != null) {\n" - "\t\t\t\treturn configuredSiteName;\n" - "\t\t\t}\n" - "\t\t}\n" - "\n" - + fallback - ) - replace_once(MANAGER, fallback, fallback_with_config) - - replace_once( - MANAGER, - "\t\tif (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName)) {", - "\t\tif (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName)\n" - "\t\t\t\t&& !hasConfiguredVoteSite(siteName)) {", - ) - - has_site = ( - "\tpublic boolean hasVoteSite(String site) {\n" - "\t\tString siteName = getVoteSiteName(false, site);\n" - "\n" - "\t\tfor (VoteSite voteSite : getVoteSites()) {" - ) - has_site_with_guard = ( - "\tpublic boolean hasVoteSite(String site) {\n" - "\t\tString siteName = getVoteSiteName(false, site);\n" - "\t\tif (siteName == null) {\n" - "\t\t\treturn false;\n" - "\t\t}\n" - "\n" - "\t\tfor (VoteSite voteSite : getVoteSites()) {" - ) - replace_once(MANAGER, has_site, has_site_with_guard) - - replace_once( - VOTIFIER, - "\t\t\t\t\tboolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr);", - "\t\t\t\t\tboolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr)\n" - "\t\t\t\t\t\t\t&& !plugin.getVoteSiteManager().hasConfiguredVoteSite(voteSiteNameStr);", - ) - - for path in (CONFIG, MANAGER, VOTIFIER): - text = path.read_text(encoding="utf-8") - if "getRawVoteSiteNames" not in text and path == CONFIG: - raise RuntimeError("ConfigVoteSites implementation was not applied") - if "hasConfiguredVoteSite" not in text and path in (MANAGER, VOTIFIER): - raise RuntimeError(f"Configured-site guard was not applied to {path}") - - # Restore the normal workflow in the commit produced after verification. - import subprocess - subprocess.run(["git", "fetch", "origin", "master", "--depth=1"], check=True) - normal_workflow = subprocess.check_output( - ["git", "show", "origin/master:.github/workflows/maven.yml"] - ) - Path(".github/workflows/maven.yml").write_bytes(normal_workflow) - - name: Build with Maven run: mvn -B -f VotingPlugin/pom.xml package - - name: Commit verified disabled vote-site fix - if: github.event_name == 'pull_request' && github.head_ref == 'tests/disabled-vote-site-autocreation' - shell: bash - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - git add \ - .github/workflows/maven.yml \ - VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java \ - VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java \ - VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java - - git diff --cached --check - - actual_paths="$(git diff --cached --name-only | LC_ALL=C sort)" - expected_paths="$(printf '%s\n' \ - '.github/workflows/maven.yml' \ - 'VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java' \ - 'VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java' \ - 'VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java' \ - | LC_ALL=C sort)" - - if [[ "${actual_paths}" != "${expected_paths}" ]]; then - printf 'Unexpected changed paths:\n%s\n' "${actual_paths}" >&2 - exit 1 - fi - - git commit -m "Prevent disabled vote sites from being auto-created" - git push origin HEAD:tests/disabled-vote-site-autocreation - # Only submit dependency snapshot on push (not on pull_request) - name: Submit Dependency Snapshot if: github.event_name == 'push' diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java index 03b0ace8e..3c0f70f5e 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java @@ -403,6 +403,24 @@ public int compare(VoteSite v1, VoteSite v2) { return voteSites; } + /** + * Gets configured vote-site section keys without filtering by Enabled or + * validating the site's settings. + * + * @return the configured vote-site section keys + */ + public ArrayList getRawVoteSiteNames() { + ArrayList siteNames = new ArrayList<>(); + + if (!getData().isConfigurationSection("VoteSites")) { + return siteNames; + } + + siteNames = ArrayUtils.convert(getData().getConfigurationSection("VoteSites").getKeys(false)); + siteNames.removeIf(siteName -> !getData().isConfigurationSection("VoteSites." + siteName)); + return siteNames; + } + /** * Gets the names of vote sites. * diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java index 6bd11cb98..d961140e7 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java @@ -92,7 +92,8 @@ public void run() { } String voteSiteNameStr = plugin.getVoteSiteManager().getVoteSiteName(false, voteSite, matchSite); - boolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr); + boolean createSite = !plugin.getVoteSiteManager().hasVoteSite(voteSiteNameStr) + && !plugin.getVoteSiteManager().hasConfiguredVoteSite(voteSiteNameStr); String serviceSite = voteSite; diff --git a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java index 109c089ec..9592e034a 100644 --- a/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java +++ b/VotingPlugin/src/main/java/com/bencodez/votingplugin/votesites/VoteSiteManager.java @@ -82,6 +82,60 @@ public String normalizeVoteSiteKey(String name) { return name.replaceAll("[\\.\\s]+", "_"); } + /** + * Resolves identifiers against every configured vote-site section, including + * disabled sites. + * + * @param identifiers vote-site keys, service sites, or display names + * @return the configured vote-site key, or null when no configuration matches + */ + private String getConfiguredVoteSiteName(String... identifiers) { + if (identifiers == null) { + return null; + } + + ArrayList configuredSites = plugin.getConfigVoteSites().getRawVoteSiteNames(); + if (configuredSites == null || configuredSites.isEmpty()) { + return null; + } + + for (String identifier : identifiers) { + if (identifier == null || identifier.isEmpty()) { + continue; + } + + String normalizedIdentifier = normalizeVoteSiteKey(identifier); + for (String siteName : configuredSites) { + if (siteName == null) { + continue; + } + + String serviceSite = plugin.getConfigVoteSites().getServiceSite(siteName); + String displayName = plugin.getConfigVoteSites().getDisplayName(siteName); + if (siteName.equalsIgnoreCase(identifier) || siteName.equalsIgnoreCase(normalizedIdentifier) + || (serviceSite != null && !serviceSite.isEmpty() + && serviceSite.equalsIgnoreCase(identifier)) + || (displayName != null && !displayName.isEmpty() + && displayName.equalsIgnoreCase(identifier))) { + return siteName; + } + } + } + + return null; + } + + /** + * Checks whether an identifier belongs to any configured vote site, including + * a disabled site. + * + * @param identifiers vote-site identifiers + * @return true when a configured vote-site section matches + */ + public boolean hasConfiguredVoteSite(String... identifiers) { + return getConfiguredVoteSiteName(identifiers) != null; + } + /** * Attempts to map a URL, display name, or key to the configured vote site key. * @@ -91,6 +145,10 @@ public String normalizeVoteSiteKey(String name) { * found */ public String getVoteSiteName(boolean checkEnabled, String... urls) { + if (urls == null) { + return null; + } + for (String url : urls) { if (url == null) { return null; @@ -119,6 +177,13 @@ public String getVoteSiteName(boolean checkEnabled, String... urls) { } } + if (!checkEnabled) { + String configuredSiteName = getConfiguredVoteSiteName(urls); + if (configuredSiteName != null) { + return configuredSiteName; + } + } + for (String url : urls) { return url; } @@ -147,7 +212,8 @@ public VoteSite getVoteSite(String site, boolean checkEnabled) { } } - if (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName)) { + if (plugin.getConfigFile().isAutoCreateVoteSites() && !hasVoteSite(siteName) + && !hasConfiguredVoteSite(siteName)) { if (!ServiceSiteValidator.isValid(siteName)) { plugin.getLogger().warning("Unable to auto-create vote site with unsupported name '" + ServiceSiteValidator.sanitizeForLog(siteName) + "'"); @@ -214,6 +280,9 @@ public String getVoteSiteServiceSite(String name) { */ public boolean hasVoteSite(String site) { String siteName = getVoteSiteName(false, site); + if (siteName == null) { + return false; + } for (VoteSite voteSite : getVoteSites()) { if (voteSite.getKey().equalsIgnoreCase(siteName)) { From 35135b194ce30539370c7c8aff5b3bf7d2b9c831 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 17 Aug 2026 18:05:26 -0600 Subject: [PATCH 7/7] Remove temporary CRLF validation metadata --- .gitattributes | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 243400ff2..000000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -VotingPlugin/src/main/java/com/bencodez/votingplugin/config/ConfigVoteSites.java whitespace=cr-at-eol -VotingPlugin/src/main/java/com/bencodez/votingplugin/listeners/VotiferEvent.java whitespace=cr-at-eol