diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 875b35587..345803a49 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -44,7 +44,8 @@ Valid examples are:
## Translations
-If you want to contribute to the translations of this project, please use [Crowdin](https://crowdin.com/project/refined-storage-2).
+If you want to contribute to the translations of this project, please
+use [Crowdin](https://crowdin.com/project/refined-storage-2).
## Versioning
@@ -126,8 +127,25 @@ at all times.
They ensure that there are no regressions, act as general documentation for the codebase,
and ensure that the project can evolve over time.
-To avoid brittle tests, tests need to validate behavior. A test cannot rely on the internal code structure, so most
-mocking should be avoided.
+To avoid brittle tests, tests need to validate behavior. A test cannot rely on the internal code structure, so most use
+of mocking should be avoided.
+
+### Unit testing
+
+Tests in the API modules are regular unit tests. Don't see a "unit" here as a code unit, but as a unit of behavior.
+
+> Don't isolate code by using mocking, even in a unit test.
+
+These tests don't rely on, nor know about, Minecraft.
+
+Additionally, tests in the `refinedstorage2-network` module use the `refinedstorage2-network-test` JUnit plugin to easily set up networks for testing.
+
+### Integration testing
+
+To test the entire chain from Minecraft to the API modules, we use integration tests. These tests are located in the
+test source set of the `refinedstorage2-platform-forge` module.
+
+We write these integration tests as Minecraft gametests using the NeoForge testing framework.
### Test coverage
@@ -202,6 +220,7 @@ The build workflow takes care of the following:
- Running a Gradle build, running our tests in the process and generating an aggregated code coverage report for the API
modules.
+- Running Minecraft gametests.
- Analyzing the code on SonarQube.
> Because of
> [limitations with SonarQube](https://portal.productboard.com/sonarsource/1-sonarcloud/c/50-sonarcloud-analyzes-external-pull-request),
@@ -249,18 +268,18 @@ Refined Storage 2 is split up into various modules.
Most modules aren't dependent on Minecraft or a mod loader. Only modules that start
with `refinedstorage2-platform-*` have dependencies on Minecraft.
-| Name | Use in addons | Description |
-|-----------------------------------|---------------|---------------------------------------------------------------------------------------|
-| `refinedstorage2-core-api` | ✔️ | Contains some utility classes and enums. |
-| `refinedstorage2-grid-api` | ✔️ | Contains Grid related functionality. |
-| `refinedstorage2-network-api` | ✔️ | Contains storage network related functionality. |
-| `refinedstorage2-network` | ❌ | Contains implementations of `refinedstorage2-network-api`. |
-| `refinedstorage2-network-test` | ✔️ | JUnit extension which helps with setting up a network and a network node for testing. |
-| `refinedstorage2-query-parser` | ✔️ | A query parser, contains a lexer and parser. Only used for Grid query parsing. |
-| `refinedstorage2-resource-api` | ✔️ | Contains API for handling resources. |
-| `refinedstorage2-storage-api` | ✔️ | Contains storage related functionality. |
-| `refinedstorage2-platform-api` | ✔️ | Implements the various Refined Storage API modules for use in Minecraft. |
-| `refinedstorage2-platform-test` | ✔️ | This module is used in platform tests for various Minecraft related helpers. |
-| `refinedstorage2-platform-fabric` | ❌ | The platform module for Fabric. This module contains Fabric specific code. |
-| `refinedstorage2-platform-forge` | ❌ | The platform module for Forge. This module contains Forge specific code. |
-| `refinedstorage2-platform-common` | ❌ | Common mod code. Most gameplay code is in here. |
+| Name | Use in addons | Description |
+|-----------------------------------|---------------|----------------------------------------------------------------------------------------------------|
+| `refinedstorage2-core-api` | ✔️ | Contains some utility classes and enums. |
+| `refinedstorage2-grid-api` | ✔️ | Contains Grid related functionality. |
+| `refinedstorage2-network-api` | ✔️ | Contains storage network related functionality. |
+| `refinedstorage2-network` | ❌ | Contains implementations of `refinedstorage2-network-api`. |
+| `refinedstorage2-network-test` | ✔️ | JUnit extension which helps with setting up a network and a network node for testing. |
+| `refinedstorage2-query-parser` | ✔️ | A query parser, contains a lexer and parser. Only used for Grid query parsing. |
+| `refinedstorage2-resource-api` | ✔️ | Contains API for handling resources. |
+| `refinedstorage2-storage-api` | ✔️ | Contains storage related functionality. |
+| `refinedstorage2-platform-api` | ✔️ | Implements the various Refined Storage API modules for use in Minecraft. |
+| `refinedstorage2-platform-test` | ✔️ | This module is used in platform tests for various Minecraft related helpers. |
+| `refinedstorage2-platform-fabric` | ❌ | The platform module for Fabric. This module contains Fabric specific code. |
+| `refinedstorage2-platform-forge` | ❌ | The platform module for Forge. This module contains Forge specific code and the integration tests. |
+| `refinedstorage2-platform-common` | ❌ | Common mod code. Most gameplay code is in here. |
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bfd3a654c..94f94e34b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,5 +8,7 @@ on:
types: [ opened, synchronize, reopened ]
jobs:
build:
- uses: refinedmods/refinedarchitect/.github/workflows/build.yml@v0.11.5
+ uses: refinedmods/refinedarchitect/.github/workflows/build.yml@v0.12.1
+ with:
+ gametests: true
secrets: inherit
diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml
index b34ffa667..6fad3ac91 100644
--- a/.github/workflows/draft-release.yml
+++ b/.github/workflows/draft-release.yml
@@ -17,7 +17,7 @@ on:
type: string
jobs:
draft:
- uses: refinedmods/refinedarchitect/.github/workflows/draft-release.yml@v0.11.5
+ uses: refinedmods/refinedarchitect/.github/workflows/draft-release.yml@v0.12.1
with:
release-type: ${{ inputs.release-type }}
version-number-override: ${{ inputs.version-number-override }}
diff --git a/.github/workflows/issue-for-unsupported-version.yml b/.github/workflows/issue-for-unsupported-version.yml
index 7aa68ecb9..1664acf15 100644
--- a/.github/workflows/issue-for-unsupported-version.yml
+++ b/.github/workflows/issue-for-unsupported-version.yml
@@ -4,4 +4,4 @@ on:
types: [ labeled, unlabeled, reopened ]
jobs:
unsupported-labeler:
- uses: refinedmods/refinedarchitect/.github/workflows/issue-for-unsupported-version.yml@v0.11.5
\ No newline at end of file
+ uses: refinedmods/refinedarchitect/.github/workflows/issue-for-unsupported-version.yml@v0.12.1
\ No newline at end of file
diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
index bd9b29525..cf971feb5 100644
--- a/.github/workflows/publish-release.yml
+++ b/.github/workflows/publish-release.yml
@@ -7,7 +7,7 @@ on:
- closed
jobs:
publish-release:
- uses: refinedmods/refinedarchitect/.github/workflows/publish-release.yml@v0.11.5
+ uses: refinedmods/refinedarchitect/.github/workflows/publish-release.yml@v0.12.1
secrets: inherit
with:
project-name: 'Refined Storage'
diff --git a/.github/workflows/resolved-issue-locking.yml b/.github/workflows/resolved-issue-locking.yml
index cbfb9219d..60236446c 100644
--- a/.github/workflows/resolved-issue-locking.yml
+++ b/.github/workflows/resolved-issue-locking.yml
@@ -4,4 +4,4 @@ on:
- cron: '0 0 * * *'
jobs:
lock:
- uses: refinedmods/refinedarchitect/.github/workflows/resolved-issue-locking.yml@v0.11.5
\ No newline at end of file
+ uses: refinedmods/refinedarchitect/.github/workflows/resolved-issue-locking.yml@v0.12.1
\ No newline at end of file
diff --git a/.github/workflows/validate-branch-name.yml b/.github/workflows/validate-branch-name.yml
index 7f4ce5e06..c862e1c82 100644
--- a/.github/workflows/validate-branch-name.yml
+++ b/.github/workflows/validate-branch-name.yml
@@ -2,4 +2,4 @@ name: Validate branch name
on: [ pull_request ]
jobs:
validate-branch-name:
- uses: refinedmods/refinedarchitect/.github/workflows/validate-branch-name.yml@v0.11.5
\ No newline at end of file
+ uses: refinedmods/refinedarchitect/.github/workflows/validate-branch-name.yml@v0.12.1
\ No newline at end of file
diff --git a/.github/workflows/validate-changelog.yml b/.github/workflows/validate-changelog.yml
index 8b90c5051..8c6b2e8de 100644
--- a/.github/workflows/validate-changelog.yml
+++ b/.github/workflows/validate-changelog.yml
@@ -4,4 +4,4 @@ on:
types: [ opened, synchronize, reopened, ready_for_review, labeled, unlabeled ]
jobs:
validate-changelog:
- uses: refinedmods/refinedarchitect/.github/workflows/validate-changelog.yml@v0.11.5
\ No newline at end of file
+ uses: refinedmods/refinedarchitect/.github/workflows/validate-changelog.yml@v0.12.1
\ No newline at end of file
diff --git a/.github/workflows/validate-commit-messages.yml b/.github/workflows/validate-commit-messages.yml
index d8d12de6b..306fbb8c3 100644
--- a/.github/workflows/validate-commit-messages.yml
+++ b/.github/workflows/validate-commit-messages.yml
@@ -2,4 +2,4 @@ name: Validate commit messages
on: [ pull_request ]
jobs:
validate-commit-messages:
- uses: refinedmods/refinedarchitect/.github/workflows/validate-commit-messages.yml@v0.11.5
\ No newline at end of file
+ uses: refinedmods/refinedarchitect/.github/workflows/validate-commit-messages.yml@v0.12.1
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml
index b0d870283..8b6e49245 100644
--- a/config/checkstyle/checkstyle-suppressions.xml
+++ b/config/checkstyle/checkstyle-suppressions.xml
@@ -10,4 +10,5 @@
+
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 1af9e0930..b82aa23a4 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/constructordestructor/ConstructorBlockEntity.java b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/constructordestructor/ConstructorBlockEntity.java
index 4c870f75e..45cf2e459 100644
--- a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/constructordestructor/ConstructorBlockEntity.java
+++ b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/constructordestructor/ConstructorBlockEntity.java
@@ -57,7 +57,7 @@ public ConstructorBlockEntity(final BlockPos pos, final BlockState state) {
}
@Override
- protected void setFilters(final List filters) {
+ public void setFilters(final List filters) {
this.tasks.clear();
this.tasks.addAll(filters.stream().map(TaskImpl::new).toList());
}
diff --git a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/exporter/ExporterBlockEntity.java b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/exporter/ExporterBlockEntity.java
index 5ea3d2f31..2b911edb6 100644
--- a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/exporter/ExporterBlockEntity.java
+++ b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/exporter/ExporterBlockEntity.java
@@ -95,7 +95,7 @@ protected void setTaskExecutor(final TaskExecutor filters) {
+ public void setFilters(final List filters) {
mainNode.setFilters(filters);
}
diff --git a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/AbstractDirectionalBlock.java b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/AbstractDirectionalBlock.java
index b8f86a1c7..b61083c3c 100644
--- a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/AbstractDirectionalBlock.java
+++ b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/AbstractDirectionalBlock.java
@@ -70,6 +70,10 @@ public Direction extractDirection(@Nullable final BlockState state) {
return getDirectionType().extractDirection(direction);
}
+ public BlockState rotated(final T direction) {
+ return defaultBlockState().setValue(getDirectionType().getProperty(), direction);
+ }
+
public static boolean doesBlockStateChangeWarrantNetworkNodeUpdate(final BlockState oldBlockState,
final BlockState newBlockState) {
if (!(newBlockState.getBlock() instanceof AbstractDirectionalBlock> newDirectionalBlock)) {
diff --git a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/network/AbstractSchedulingNetworkNodeContainerBlockEntity.java b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/network/AbstractSchedulingNetworkNodeContainerBlockEntity.java
index df09b565f..dac1392be 100644
--- a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/network/AbstractSchedulingNetworkNodeContainerBlockEntity.java
+++ b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/network/AbstractSchedulingNetworkNodeContainerBlockEntity.java
@@ -82,5 +82,5 @@ public void writeScreenOpeningData(final ServerPlayer player, final FriendlyByte
protected abstract void setTaskExecutor(TaskExecutor taskExecutor);
- protected abstract void setFilters(List filters);
+ public abstract void setFilters(List filters);
}
diff --git a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/network/AbstractUpgradeableNetworkNodeContainerBlockEntity.java b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/network/AbstractUpgradeableNetworkNodeContainerBlockEntity.java
index 6939537ae..27f36f9ed 100644
--- a/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/network/AbstractUpgradeableNetworkNodeContainerBlockEntity.java
+++ b/refinedstorage2-platform-common/src/main/java/com/refinedmods/refinedstorage2/platform/common/support/network/AbstractUpgradeableNetworkNodeContainerBlockEntity.java
@@ -10,7 +10,6 @@
import java.util.ArrayList;
import java.util.List;
-import com.google.common.util.concurrent.RateLimiter;
import net.minecraft.core.BlockPos;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
@@ -33,7 +32,8 @@ public abstract class AbstractUpgradeableNetworkNodeContainerBlockEntity type,
@@ -52,7 +52,7 @@ protected AbstractUpgradeableNetworkNodeContainerBlockEntity(
@Override
public final void doWork() {
- if (rateLimiter.tryAcquire()) {
+ if (workTicks++ % workTickRate == 0) {
super.doWork();
postDoWork();
}
@@ -105,16 +105,12 @@ public void load(final CompoundTag tag) {
private void configureAccordingToUpgrades() {
LOGGER.debug("Reconfiguring {} for upgrades", getBlockPos());
final int amountOfSpeedUpgrades = upgradeContainer.getAmount(Items.INSTANCE.getSpeedUpgrade());
- this.rateLimiter = createRateLimiter(amountOfSpeedUpgrades);
+ this.workTickRate = (amountOfSpeedUpgrades + 1) * 20;
this.setEnergyUsage(upgradeContainer.getEnergyUsage());
}
protected abstract void setEnergyUsage(long upgradeEnergyUsage);
- private static RateLimiter createRateLimiter(final int amountOfSpeedUpgrades) {
- return RateLimiter.create((double) amountOfSpeedUpgrades + 1);
- }
-
@Override
public NonNullList getDrops() {
final NonNullList drops = NonNullList.create();
diff --git a/refinedstorage2-platform-forge/src/test/java/com/refinedmods/refinedstorage2/platform/forge/ConstructorTest.java b/refinedstorage2-platform-forge/src/test/java/com/refinedmods/refinedstorage2/platform/forge/ConstructorTest.java
new file mode 100644
index 000000000..88523fe7d
--- /dev/null
+++ b/refinedstorage2-platform-forge/src/test/java/com/refinedmods/refinedstorage2/platform/forge/ConstructorTest.java
@@ -0,0 +1,51 @@
+package com.refinedmods.refinedstorage2.platform.forge;
+
+import com.refinedmods.refinedstorage2.api.resource.ResourceAmount;
+import com.refinedmods.refinedstorage2.platform.common.constructordestructor.ConstructorBlockEntity;
+import com.refinedmods.refinedstorage2.platform.common.storage.ItemStorageType;
+import com.refinedmods.refinedstorage2.platform.common.util.IdentifierUtil;
+
+import java.util.List;
+
+import net.minecraft.core.Direction;
+import net.minecraft.gametest.framework.GameTest;
+import net.minecraft.gametest.framework.GameTestHelper;
+import net.minecraft.world.level.block.Blocks;
+import net.neoforged.neoforge.gametest.GameTestHolder;
+import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
+
+import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.DIRT;
+import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.RSBLOCKS;
+import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.itemIsInserted;
+import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.requireBlockEntity;
+import static com.refinedmods.refinedstorage2.platform.forge.GameTestUtil.storageMustContainExactly;
+import static net.minecraft.core.BlockPos.ZERO;
+
+@GameTestHolder(IdentifierUtil.MOD_ID)
+@PrefixGameTestTemplate(false)
+public final class ConstructorTest {
+ private ConstructorTest() {
+ }
+
+ @GameTest(template = "empty_15x15")
+ public static void shouldPlaceBlock(final GameTestHelper helper) {
+ // Arrange
+ helper.setBlock(ZERO.above(), RSBLOCKS.getCreativeController().getDefault());
+ helper.setBlock(ZERO.above().above(), RSBLOCKS.getConstructor().getDefault().rotated(Direction.EAST));
+ helper.setBlock(
+ ZERO.above().above().above(),
+ RSBLOCKS.getItemStorageBlock(ItemStorageType.Variant.ONE_K)
+ );
+
+ final var seq = helper.startSequence();
+ seq.thenWaitUntil(itemIsInserted(helper, ZERO.above().above(), DIRT, 10));
+
+ // Act
+ requireBlockEntity(helper, ZERO.above().above(), ConstructorBlockEntity.class).setFilters(List.of(DIRT));
+
+ // Assert
+ seq.thenWaitUntil(() -> helper.assertBlockPresent(Blocks.DIRT, ZERO.above().above().east()))
+ .thenWaitUntil(storageMustContainExactly(helper, ZERO.above().above(), new ResourceAmount(DIRT, 9)))
+ .thenSucceed();
+ }
+}
diff --git a/refinedstorage2-platform-forge/src/test/java/com/refinedmods/refinedstorage2/platform/forge/GameTestUtil.java b/refinedstorage2-platform-forge/src/test/java/com/refinedmods/refinedstorage2/platform/forge/GameTestUtil.java
new file mode 100644
index 000000000..26ffd3042
--- /dev/null
+++ b/refinedstorage2-platform-forge/src/test/java/com/refinedmods/refinedstorage2/platform/forge/GameTestUtil.java
@@ -0,0 +1,104 @@
+package com.refinedmods.refinedstorage2.platform.forge;
+
+import com.refinedmods.refinedstorage2.api.core.Action;
+import com.refinedmods.refinedstorage2.api.network.Network;
+import com.refinedmods.refinedstorage2.api.network.node.NetworkNode;
+import com.refinedmods.refinedstorage2.api.network.storage.StorageNetworkComponent;
+import com.refinedmods.refinedstorage2.api.resource.ResourceAmount;
+import com.refinedmods.refinedstorage2.api.storage.EmptyActor;
+import com.refinedmods.refinedstorage2.platform.api.support.network.AbstractNetworkNodeContainerBlockEntity;
+import com.refinedmods.refinedstorage2.platform.common.content.Blocks;
+import com.refinedmods.refinedstorage2.platform.common.support.resource.ItemResource;
+
+import java.util.Arrays;
+import javax.annotation.Nullable;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.gametest.framework.GameTestAssertException;
+import net.minecraft.gametest.framework.GameTestHelper;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.block.entity.BlockEntity;
+
+public final class GameTestUtil {
+ public static final ItemResource DIRT = ItemResource.ofItemStack(new ItemStack(Items.DIRT));
+ public static final Blocks RSBLOCKS = Blocks.INSTANCE;
+
+ private GameTestUtil() {
+ }
+
+ @Nullable
+ public static Network getNetwork(final net.minecraft.gametest.framework.GameTestHelper helper, final BlockPos pos) {
+ try {
+ final var be = requireBlockEntity(helper, pos, AbstractNetworkNodeContainerBlockEntity.class);
+ final var field = AbstractNetworkNodeContainerBlockEntity.class.getDeclaredField("mainNode");
+ field.setAccessible(true);
+ final NetworkNode mainNode = (NetworkNode) field.get(be);
+ return mainNode.getNetwork();
+ } catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException | SecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Runnable itemIsInserted(final net.minecraft.gametest.framework.GameTestHelper helper,
+ final BlockPos networkPos,
+ final ItemResource resource,
+ final long amount) {
+ return () -> {
+ final Network network = getNetwork(helper, networkPos);
+ helper.assertTrue(network != null && network.getComponent(StorageNetworkComponent.class)
+ .insert(resource, amount, Action.EXECUTE, EmptyActor.INSTANCE) == amount,
+ "Item couldn't be inserted"
+ );
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T requireBlockEntity(
+ final GameTestHelper helper,
+ final BlockPos pos,
+ final Class clazz
+ ) {
+ final BlockEntity blockEntity = helper.getBlockEntity(pos);
+ if (blockEntity == null) {
+ throw new GameTestAssertException("Block entity not found at " + pos);
+ }
+ if (!clazz.isInstance(blockEntity)) {
+ throw new GameTestAssertException(
+ "Expected block entity of type " + clazz + " but was " + blockEntity.getClass()
+ );
+ }
+ return (T) blockEntity;
+ }
+
+ public static Runnable storageMustContainExactly(final net.minecraft.gametest.framework.GameTestHelper helper,
+ final BlockPos networkPos,
+ final ResourceAmount... expected) {
+ return () -> {
+ final Network network = getNetwork(helper, networkPos);
+ helper.assertTrue(network != null, "Network is not found");
+ if (network == null) {
+ return;
+ }
+ final StorageNetworkComponent storage = network.getComponent(StorageNetworkComponent.class);
+ for (final ResourceAmount expectedItem : expected) {
+ final boolean contains = storage.getAll()
+ .stream()
+ .anyMatch(inStorage -> inStorage.getResource().equals(expectedItem.getResource())
+ && inStorage.getAmount() == expectedItem.getAmount());
+ if (!contains) {
+ throw new GameTestAssertException("Missing from storage: " + expectedItem);
+ }
+ }
+ for (final ResourceAmount inStorage : storage.getAll()) {
+ final boolean wasExpected = Arrays.stream(expected).anyMatch(
+ expectedItem -> expectedItem.getResource().equals(inStorage.getResource())
+ && expectedItem.getAmount() == inStorage.getAmount()
+ );
+ if (!wasExpected) {
+ throw new GameTestAssertException("Unexpected in storage: " + inStorage);
+ }
+ }
+ };
+ }
+}
diff --git a/refinedstorage2-platform-forge/src/test/resources/data/refinedstorage2/structures/empty_15x15.nbt b/refinedstorage2-platform-forge/src/test/resources/data/refinedstorage2/structures/empty_15x15.nbt
new file mode 100644
index 000000000..bec099168
Binary files /dev/null and b/refinedstorage2-platform-forge/src/test/resources/data/refinedstorage2/structures/empty_15x15.nbt differ
diff --git a/settings.gradle b/settings.gradle
index 37d224592..90c2297da 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,4 @@
-gradle.ext.refinedArchitectVersion = "0.11.5"
+gradle.ext.refinedArchitectVersion = "0.12.1"
dependencyResolutionManagement {
repositories {