Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8bca5c0
fix: cover plugin behavior and correct runtime edge cases
ryanbarlow97 Oct 7, 2026
16542e9
fix: cover configuration economy and faction workflows
ryanbarlow97 Oct 7, 2026
277475e
chore: link independently tested coverage foundation
ryanbarlow97 Oct 7, 2026
b92476e
fix: preserve state across reloads and war migrations
ryanbarlow97 Oct 7, 2026
db12ea5
fix: preserve plugin state and reject stale actions
ryanbarlow97 Oct 7, 2026
1e2ad02
Merge reviewed foundation fixes into complete coverage
ryanbarlow97 Oct 7, 2026
9e9d2ee
fix: preserve live definitions after rejected reloads
ryanbarlow97 Oct 7, 2026
38f5038
fix: preserve legacy wars and raid membership during reload
ryanbarlow97 Oct 7, 2026
c4fd66d
Merge branch 'test/coverage-foundation' into test/complete-plugin-cov…
ryanbarlow97 Oct 7, 2026
e4073ce
test: clean up failed faction fixture construction
ryanbarlow97 Oct 7, 2026
de1fab8
fix: retain guild capitals across configuration reloads
ryanbarlow97 Oct 7, 2026
c6cfca6
fix: retain guild capitals across configuration reloads
ryanbarlow97 Oct 7, 2026
5f9ad31
Merge branch 'test/coverage-foundation' into test/complete-plugin-cov…
ryanbarlow97 Oct 7, 2026
b5305f1
fix: restore dependent guild definitions after rejected reloads
ryanbarlow97 Oct 7, 2026
ca01155
fix: restore dependent guild definitions after rejected reloads
ryanbarlow97 Oct 7, 2026
f8f211e
Merge branch 'test/coverage-foundation' into test/complete-plugin-cov…
ryanbarlow97 Oct 7, 2026
63b0a65
Merge remote-tracking branch 'origin/main' into test/complete-plugin-…
ryanbarlow97 Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
branches: [main, 'test/coverage-foundation']

permissions:
contents: read
Expand Down Expand Up @@ -74,3 +74,11 @@ jobs:
name: unit-test-reports-${{ github.run_id }}-${{ github.run_attempt }}
path: target/surefire-reports/
if-no-files-found: error

- name: Upload coverage reports
if: ${{ !cancelled() && hashFiles('target/site/jacoco/jacoco.xml') != '' }}
uses: actions/upload-artifact@v7
with:
name: coverage-reports-${{ github.run_id }}-${{ github.run_attempt }}
path: target/site/jacoco/
if-no-files-found: error
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,11 @@ documentation.

The suite in `src/test` uses JUnit 5 and Mockito, mocking the Paper and plugin
APIs rather than starting a server. It checks plugin logic, not behaviour on a
live Paper server. Surefire writes reports to `target/surefire-reports/`. No
coverage gate is enforced.
live Paper server. Surefire writes reports to `target/surefire-reports/`. JaCoCo
writes HTML and XML reports to `target/site/jacoco/`; `verify` requires 100%
production line coverage with no
class or package exclusions. This is a line coverage gate, not a branch coverage
guarantee. CI uploads both test and coverage reports.

## License

Expand Down
36 changes: 35 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<version>main-SNAPSHOT</version>

<properties>
<argLine></argLine>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.proc>full</maven.compiler.proc>
Expand Down Expand Up @@ -184,6 +185,38 @@
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.15</version>
<executions>
<execution><goals><goal>prepare-agent</goal></goals></execution>
<execution>
<id>coverage-report</id>
<phase>verify</phase>
<goals><goal>report</goal></goals>
</execution>
<execution>
<id>coverage-check</id>
<phase>verify</phase>
<goals><goal>check</goal></goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>MISSEDCOUNT</value>
<maximum>0</maximum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
Expand All @@ -209,7 +242,8 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>-javaagent:${org.mockito:mockito-core:jar} -Xshare:off -Dnet.bytebuddy.experimental=true -Djdk.net.URLClassPath.disableClassPathURLCheck=true</argLine>
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar} -Xshare:off -Dnet.bytebuddy.experimental=true -Djdk.net.URLClassPath.disableClassPathURLCheck=true</argLine>
<failIfNoTests>true</failIfNoTests>
</configuration>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public class CampaignRaidData {
public String id;
public String displayName;
public String warbandIdPrefix;
public int warId;
public String battleDay;
public String attackerCoalition;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,18 +612,30 @@ public List<War> loadWars() {
for (File file : files) {
if (!file.getName().endsWith(".json")) continue;

WarData data;
War war;
try {
WarData data = JsonUtil.readJson(file, WarData.class);
data = JsonUtil.readJson(file, WarData.class);
if (data == null) continue;

War war = WarMapper.fromData(data);
if (war != null) {
wars.add(war);
}

war = WarMapper.fromData(data);
} catch (Exception e) {
e.printStackTrace();
continue;
}
if (war == null) {
java.util.logging.Logger.getLogger(Database.class.getName()).warning(
"Cannot restore war from " + file + "; keeping its file for recovery");
continue;
}
if (data.installationReferenceVersion < 1) {
net.tfminecraft.simplefactions.installation.WarInstallationMigration.migrate(war);
try {
JsonUtil.writeJsonAtomic(file, WarMapper.toData(war));
} catch (java.io.IOException e) {
throw new IllegalStateException("Cannot save migrated war " + war.getId(), e);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
wars.add(war);
}

return wars;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

public class WarData {
public int schemaVersion = 2;
public int installationReferenceVersion;
public int id;
public String status;
public String goal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ public String getId() {
return id;
}

/** Persistent identity across a transfer that changes the holder or local id. */
public String getStableKey() {
return "province:" + province + ":" + kind.getCommandName() + ":" + completedAt;
}

public String getName() {
return name;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.bukkit.Location;

import net.tfminecraft.simplefactions.loaders.InstallationConfigLoader;
import net.tfminecraft.simplefactions.Cache;
import net.tfminecraft.simplefactions.war.battle.engine.core.BattlePlacementValidator;

public final class InstallationBounds {
Expand All @@ -24,7 +25,7 @@ public static String formatDistance(double blocks) {
}

public static boolean isWithinRadius(Installation installation, Location location) {
if (installation == null || location == null) {
if (installation == null || !inInstallationWorld(location)) {
return false;
}
int radius = InstallationConfigLoader.getRadius(installation.getKind());
Expand All @@ -42,6 +43,12 @@ public static boolean isCorrectProvince(Installation installation, Location loca
}

public static int provinceAt(Location location) {
if (!inInstallationWorld(location)) return 0;
return BattlePlacementValidator.provinceAt(location);
}

private static boolean inInstallationWorld(Location location) {
return location != null && location.getWorld() != null
&& Cache.worldName != null && Cache.worldName.equals(location.getWorld().getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ public static Installation findById(String installationId) {
return null;
}
for (Faction faction : FactionManager.factions) {
if (faction == null || faction.getInstallationHandler() == null) {
continue;
}
Installation installation = faction.getInstallationHandler().getById(installationId);
if (installation != null) {
return installation;
Expand All @@ -34,9 +31,6 @@ public static Faction findHolder(String installationId) {
return null;
}
for (Faction faction : FactionManager.factions) {
if (faction == null || faction.getInstallationHandler() == null) {
continue;
}
if (faction.getInstallationHandler().getById(installationId) != null) {
return faction;
}
Expand All @@ -49,9 +43,6 @@ public static Faction findHolderOnProvince(int province) {
return null;
}
for (Faction faction : FactionManager.factions) {
if (faction == null || faction.getInstallationHandler() == null) {
continue;
}
for (Installation installation : faction.getInstallationHandler().getAll()) {
if (installation.getProvince() == province) {
return faction;
Expand All @@ -77,9 +68,6 @@ public static Installation findCovering(Location location) {
public static List<Installation> all() {
List<Installation> installations = new ArrayList<>();
for (Faction faction : FactionManager.factions) {
if (faction == null || faction.getInstallationHandler() == null) {
continue;
}
InstallationHandler handler = faction.getInstallationHandler();
installations.addAll(handler.getAll());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ public static boolean hasOperationalPort(Faction faction) {
return false;
}
InstallationHandler handler = faction.getInstallationHandler();
if (handler == null) {
return false;
}
for (Installation installation : handler.getAll()) {
if (installation != null && installation.getKind() == InstallationKind.PORT) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import net.tfminecraft.simplefactions.utils.Permissions;
import net.tfminecraft.simplefactions.war.battle.ui.BattlePermissions;
import net.tfminecraft.simplefactions.vehicles.registry.OwnershipMode;
import net.tfminecraft.simplefactions.vehicles.pool.FactionVehiclePoolService;
import net.tfminecraft.simplefactions.vehicles.registry.PlayerVehicleRecord;
import net.tfminecraft.simplefactions.vehicles.registry.PlayerVehicleRegistry;
import net.tfminecraft.vehicleframework.events.VFEntityDamageEvent;
Expand All @@ -34,7 +35,7 @@ public void onBlockBreak(BlockBreakEvent event) {
if (installation == null) {
return;
}
if (!InstallationVulnerabilityService.isVulnerable(installation.getId(), Instant.now())) {
if (!InstallationVulnerabilityService.isInstallationVulnerable(installation, Instant.now())) {
event.setCancelled(true);
}
}
Expand All @@ -49,7 +50,7 @@ public void onBlockPlace(BlockPlaceEvent event) {
if (installation == null) {
return;
}
if (!InstallationVulnerabilityService.isVulnerable(installation.getId(), Instant.now())) {
if (!InstallationVulnerabilityService.isInstallationVulnerable(installation, Instant.now())) {
event.setCancelled(true);
}
}
Expand All @@ -66,7 +67,7 @@ public void onExplosion(VFExplosionEvent event) {
|| !InstallationBounds.isCorrectProvince(installation, location)) {
continue;
}
if (!InstallationVulnerabilityService.isVulnerable(installation.getId(), now)) {
if (!InstallationVulnerabilityService.isInstallationVulnerable(installation, now)) {
event.setBlockDamage(false);
return;
}
Expand All @@ -83,28 +84,26 @@ public void onVehicleDamage(VFEntityDamageEvent event) {
return;
}
Instant now = Instant.now();
String installationId = resolveInstallationId(vehicle);
if (installationId != null && !InstallationVulnerabilityService.isVulnerable(installationId, now)) {
event.setCancelled(true);
return;
PlayerVehicleRecord record = resolveInstallationRecord(vehicle);
if (record != null) {
var owner = FactionVehiclePoolService.payingFaction(record);
Installation installation = owner != null
? owner.getInstallationHandler().getById(record.getInstallationId()) : null;
if (!InstallationVulnerabilityService.isInstallationVulnerable(installation, now)) {
event.setCancelled(true);
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Installation covering = InstallationLookup.findCovering(vehicle.getLocation());
if (covering != null && !InstallationVulnerabilityService.isVulnerable(covering.getId(), now)) {
if (covering != null && !InstallationVulnerabilityService.isInstallationVulnerable(covering, now)) {
event.setCancelled(true);
}
}

private static String resolveInstallationId(ActiveVehicle vehicle) {
if (vehicle == null) {
return null;
}
private static PlayerVehicleRecord resolveInstallationRecord(ActiveVehicle vehicle) {
PlayerVehicleRegistry registry = SimpleFactions.getVehicleRegistry();
if (registry == null) {
return null;
}
return registry.getByVehicleUuid(vehicle.getUUID())
.filter(record -> record.getMode() == OwnershipMode.INSTALLATION)
.map(PlayerVehicleRecord::getInstallationId)
.orElse(null);
}

Expand Down
Loading
Loading