package com.xfestudio.xfeservermanager.core.content;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.xfestudio.xfeservermanager.core.content.ContentDefinition.Kind.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class ContentDefinitionTest {
@Test
void catalogDefaultsAndTemplatesAreAcceptedByTheSameStrictSchema() {
ContentCatalog catalog = ContentCatalog.builtIn();
assertThat(catalog.schemaVersion()).isEqualTo(1);
assertThat(catalog.archetypes()).hasSize(19);
for (var archetype : catalog.archetypes()) {
Map<String, String> properties = new HashMap<>();
archetype.properties().forEach(property -> properties.put(property.key(), property.defaultValue()));
new ContentDefinition("xfesmcontent:" + archetype.id(), archetype.kind(), "Test", "", archetype.id(), properties, List.of());
assertThat(archetype.nameZh()).isNotBlank();
assertThat(archetype.descriptionZh()).isNotBlank();
assertThat(archetype.properties()).allMatch(property -> !property.nameZh().isBlank() && !property.descriptionZh().isBlank());
}
assertThat(catalog.templates()).hasSize(2);
assertThat(catalog.actions()).extracting(ContentCatalog.Action::id).containsExactly("trigger", "open_menu", "sit", "play_sound", "stop_sound");
}
@Test
void rejectsUnknownCapabilitiesAndUnsafeResources() {
assertThatThrownBy(() -> item("minecraft:stone", "generic", Map.of(), List.of())).hasMessageContaining("xfesmcontent");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of("nbt", "{}"), List.of())).hasMessageContaining("unsupported property");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of("texture", "https://example.com/a.png"), List.of())).hasMessageContaining("texture");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of("texture", "xfesmcontent:../secret"), List.of())).hasMessageContaining("texture");
assertThatThrownBy(() -> item("xfesmcontent:test", "script_item", Map.of(), List.of())).hasMessageContaining("unsupported content archetype");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("right_click", "java", Map.of(), false))))
.hasMessageContaining("unsupported behavior action");
}
@Test
void enforcesItemAndNumericConstraintsWithoutNaNOrOverflow() {
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("max_stack_size", "64"), List.of())).hasMessageContaining("max_stack_size");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of("durability", "100"), List.of())).hasMessageContaining("max_stack_size=1");
assertThatThrownBy(() -> item("xfesmcontent:test", "food", Map.of("durability", "100"), List.of())).hasMessageContaining("unsupported property");
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("attack_damage", "1.5"), List.of())).hasMessageContaining("integer");
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("attack_speed", "NaN"), List.of())).hasMessageContaining("attack_speed");
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("attack_speed", "1e999"), List.of())).hasMessageContaining("out of range");
item("xfesmcontent:test", "shovel", Map.of("attack_damage", "1.5"), List.of());
}
@Test
void enforcesEventApplicabilityAndRequiredReferences() {
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("right_click", "sit", Map.of(), false))))
.hasMessageContaining("not available");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("consume", "stop_sound", Map.of("sound", "minecraft:test"), false))))
.hasMessageContaining("not available");
assertThatThrownBy(() -> item("xfesmcontent:test", "food", Map.of(), List.of(behavior("consume", "stop_sound", Map.of("sound", "minecraft:test"), true))))
.hasMessageContaining("cannot cancel");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("right_click", "open_menu", Map.of(), false))))
.hasMessageContaining("menu_id");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("right_click", "stop_sound", Map.of(), false))))
.hasMessageContaining("sound");
}
@Test
void rejectsInvalidShapesAndUnsupportedAnimalAttributes() {
assertThatThrownBy(() -> new ContentDefinition("xfesmcontent:test", BLOCK, "Test", "", "solid", Map.of("shape_min_x", "16"), List.of()))
.hasMessageContaining("shape_min_x");
assertThatThrownBy(() -> new ContentDefinition("xfesmcontent:test", ENTITY, "Test", "", "cow", Map.of("attack_damage", "20"), List.of()))
.hasMessageContaining("unsupported property");
assertThatThrownBy(() -> new ContentDefinition("xfesmcontent:test", ENTITY, "Test", "", "zombie", Map.of("texture", "minecraft:entity/zombie"), List.of()))
.hasMessageContaining("unsupported property");
}
@Test
void enforcesSharedNativeAttributeLimitsBeforeMinecraftCanSilentlyClampThem() {
for (var entry : Map.of("max_health", "1025", "armor", "31", "attack_damage", "2049").entrySet()) {
assertThatThrownBy(() -> new ContentDefinition("xfesmcontent:test", ENTITY, "Test", "", "zombie",
Map.of(entry.getKey(), entry.getValue()), List.of())).hasMessageContaining("out of range");
}
new ContentDefinition("xfesmcontent:test", ENTITY, "Test", "", "zombie",
Map.of("max_health", "1024", "armor", "30", "attack_damage", "2048"), List.of());
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("attack_damage", "2044"), List.of()))
.hasMessageContaining("out of range");
assertThatThrownBy(() -> item("xfesmcontent:test", "shovel", Map.of("attack_damage", "2043.1"), List.of()))
.hasMessageContaining("out of range");
item("xfesmcontent:test", "sword", Map.of("tier", "netherite", "attack_damage", "2043"), List.of());
assertThat(ContentCatalog.archetype(ITEM, "sword").properties()).anySatisfy(property -> {
assertThat(property.key()).isEqualTo("attack_damage");
assertThat(property.descriptionZh()).contains("玩家属性", "附魔");
});
}
@Test
void takesImmutableCopiesAndEnforcesWorkspaceCapacity() {
var mutable = new HashMap<String, String>();
mutable.put("max_stack_size", "16");
ContentDefinition original = item("xfesmcontent:test", "generic", mutable, List.of());
mutable.put("max_stack_size", "32");
assertThat(original.properties()).containsEntry("max_stack_size", "16");
assertThatThrownBy(() -> original.properties().put("max_stack_size", "1")).isInstanceOf(UnsupportedOperationException.class);
assertThatThrownBy(() -> new ContentWorkspace(1, 0, true, List.of(original, original), Instant.EPOCH, "owner"))
.hasMessageContaining("duplicate content id");
List<ContentDefinition> definitions = new ArrayList<>();
for (int i = 0; i < 257; i++) definitions.add(item("xfesmcontent:item_" + i, "generic", Map.of(), List.of()));
assertThatThrownBy(() -> new ContentWorkspace(1, 0, true, definitions, Instant.EPOCH, "owner")).hasMessageContaining("limit exceeded");
var duplicate = behavior("right_click", "play_sound", Map.of("sound", "minecraft:test"), false);
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(duplicate, duplicate))).hasMessageContaining("duplicate behavior");
}
private static ContentDefinition item(String id, String archetype, Map<String, String> properties, List<ContentDefinition.Behavior> behaviors) {
return new ContentDefinition(id, ITEM, "Test", "", archetype, properties, behaviors);
}
private static ContentDefinition.Behavior behavior(String event, String action, Map<String, String> parameters, boolean cancel) {
return new ContentDefinition.Behavior("behavior", event, action, parameters, 0, cancel);
}
}
package com.xfestudio.xfeservermanager.core.content;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.xfestudio.xfeservermanager.core.content.ContentDefinition.Kind.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class ContentDefinitionTest {
@Test
void catalogDefaultsAndTemplatesAreAcceptedByTheSameStrictSchema() {
ContentCatalog catalog = ContentCatalog.builtIn();
assertThat(catalog.schemaVersion()).isEqualTo(1);
assertThat(catalog.archetypes()).hasSize(19);
for (var archetype : catalog.archetypes()) {
Map<String, String> properties = new HashMap<>();
archetype.properties().forEach(property -> properties.put(property.key(), property.defaultValue()));
new ContentDefinition("xfesmcontent:" + archetype.id(), archetype.kind(), "Test", "", archetype.id(), properties, List.of());
assertThat(archetype.nameZh()).isNotBlank();
assertThat(archetype.descriptionZh()).isNotBlank();
assertThat(archetype.properties()).allMatch(property -> !property.nameZh().isBlank() && !property.descriptionZh().isBlank());
}
assertThat(catalog.templates()).hasSize(2);
assertThat(catalog.actions()).extracting(ContentCatalog.Action::id).containsExactly("trigger", "open_menu", "sit", "play_sound", "stop_sound");
}
@Test
void rejectsUnknownCapabilitiesAndUnsafeResources() {
assertThatThrownBy(() -> item("minecraft:stone", "generic", Map.of(), List.of())).hasMessageContaining("xfesmcontent");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of("nbt", "{}"), List.of())).hasMessageContaining("unsupported property");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of("texture", "https://example.com/a.png"), List.of())).hasMessageContaining("texture");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of("texture", "xfesmcontent:../secret"), List.of())).hasMessageContaining("texture");
assertThatThrownBy(() -> item("xfesmcontent:test", "script_item", Map.of(), List.of())).hasMessageContaining("unsupported content archetype");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("right_click", "java", Map.of(), false))))
.hasMessageContaining("unsupported behavior action");
}
@Test
void enforcesItemAndNumericConstraintsWithoutNaNOrOverflow() {
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("max_stack_size", "64"), List.of())).hasMessageContaining("max_stack_size");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of("durability", "100"), List.of())).hasMessageContaining("max_stack_size=1");
assertThatThrownBy(() -> item("xfesmcontent:test", "food", Map.of("durability", "100"), List.of())).hasMessageContaining("unsupported property");
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("attack_damage", "1.5"), List.of())).hasMessageContaining("integer");
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("attack_speed", "NaN"), List.of())).hasMessageContaining("attack_speed");
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("attack_speed", "1e999"), List.of())).hasMessageContaining("out of range");
item("xfesmcontent:test", "shovel", Map.of("attack_damage", "1.5"), List.of());
}
@Test
void enforcesEventApplicabilityAndRequiredReferences() {
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("right_click", "sit", Map.of(), false))))
.hasMessageContaining("not available");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("consume", "stop_sound", Map.of("sound", "minecraft:test"), false))))
.hasMessageContaining("not available");
assertThatThrownBy(() -> item("xfesmcontent:test", "food", Map.of(), List.of(behavior("consume", "stop_sound", Map.of("sound", "minecraft:test"), true))))
.hasMessageContaining("cannot cancel");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("right_click", "open_menu", Map.of(), false))))
.hasMessageContaining("menu_id");
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(behavior("right_click", "stop_sound", Map.of(), false))))
.hasMessageContaining("sound");
}
@Test
void rejectsInvalidShapesAndUnsupportedAnimalAttributes() {
assertThatThrownBy(() -> new ContentDefinition("xfesmcontent:test", BLOCK, "Test", "", "solid", Map.of("shape_min_x", "16"), List.of()))
.hasMessageContaining("shape_min_x");
assertThatThrownBy(() -> new ContentDefinition("xfesmcontent:test", ENTITY, "Test", "", "cow", Map.of("attack_damage", "20"), List.of()))
.hasMessageContaining("unsupported property");
assertThatThrownBy(() -> new ContentDefinition("xfesmcontent:test", ENTITY, "Test", "", "zombie", Map.of("texture", "minecraft:entity/zombie"), List.of()))
.hasMessageContaining("unsupported property");
}
@Test
void enforcesSharedNativeAttributeLimitsBeforeMinecraftCanSilentlyClampThem() {
for (var entry : Map.of("max_health", "1025", "armor", "31", "attack_damage", "2049").entrySet()) {
assertThatThrownBy(() -> new ContentDefinition("xfesmcontent:test", ENTITY, "Test", "", "zombie",
Map.of(entry.getKey(), entry.getValue()), List.of())).hasMessageContaining("out of range");
}
new ContentDefinition("xfesmcontent:test", ENTITY, "Test", "", "zombie",
Map.of("max_health", "1024", "armor", "30", "attack_damage", "2048"), List.of());
assertThatThrownBy(() -> item("xfesmcontent:test", "sword", Map.of("attack_damage", "2044"), List.of()))
.hasMessageContaining("out of range");
assertThatThrownBy(() -> item("xfesmcontent:test", "shovel", Map.of("attack_damage", "2043.1"), List.of()))
.hasMessageContaining("out of range");
item("xfesmcontent:test", "sword", Map.of("tier", "netherite", "attack_damage", "2043"), List.of());
assertThat(ContentCatalog.archetype(ITEM, "sword").properties()).anySatisfy(property -> {
assertThat(property.key()).isEqualTo("attack_damage");
assertThat(property.descriptionZh()).contains("玩家属性", "附魔");
});
}
@Test
void takesImmutableCopiesAndEnforcesWorkspaceCapacity() {
var mutable = new HashMap<String, String>();
mutable.put("max_stack_size", "16");
ContentDefinition original = item("xfesmcontent:test", "generic", mutable, List.of());
mutable.put("max_stack_size", "32");
assertThat(original.properties()).containsEntry("max_stack_size", "16");
assertThatThrownBy(() -> original.properties().put("max_stack_size", "1")).isInstanceOf(UnsupportedOperationException.class);
assertThatThrownBy(() -> new ContentWorkspace(1, 0, true, List.of(original, original), Instant.EPOCH, "owner"))
.hasMessageContaining("duplicate content id");
List<ContentDefinition> definitions = new ArrayList<>();
for (int i = 0; i < 257; i++) definitions.add(item("xfesmcontent:item_" + i, "generic", Map.of(), List.of()));
assertThatThrownBy(() -> new ContentWorkspace(1, 0, true, definitions, Instant.EPOCH, "owner")).hasMessageContaining("limit exceeded");
var duplicate = behavior("right_click", "play_sound", Map.of("sound", "minecraft:test"), false);
assertThatThrownBy(() -> item("xfesmcontent:test", "generic", Map.of(), List.of(duplicate, duplicate))).hasMessageContaining("duplicate behavior");
}
private static ContentDefinition item(String id, String archetype, Map<String, String> properties, List<ContentDefinition.Behavior> behaviors) {
return new ContentDefinition(id, ITEM, "Test", "", archetype, properties, behaviors);
}
private static ContentDefinition.Behavior behavior(String event, String action, Map<String, String> parameters, boolean cancel) {
return new ContentDefinition.Behavior("behavior", event, action, parameters, 0, cancel);
}
}