package com.tiedup.remake.v2.furniture; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; /** * Tests for the pure-logic authorization core in {@link FurnitureAuthPredicate}. * *
These tests pin down the truth tables for the lock and force-mount gates * without touching any Minecraft API. The MC-aware wrappers ({@code canLockUnlock}, * {@code canForceMount}) extract booleans from live entities and delegate here, * so full-stack validation is manual (in-game).
* *The 2026-04-17 audit documented three divergent auth paths (BUG-002, BUG-003) * that this predicate now unifies. If anyone re-introduces the drift, the * isAuthorizedForLock / isAuthorizedForForceMount methods will catch it here.
*/ class FurnitureAuthPredicateTest { @Nested @DisplayName("isAuthorizedForLock — AND of 4 gates") class LockAuthorization { @Test @DisplayName("all four gates true → authorized") void allTrue() { assertTrue( FurnitureAuthPredicate.isAuthorizedForLock( true, true, true, true ) ); } @Test @DisplayName("missing master key → denied") void missingKey() { assertFalse( FurnitureAuthPredicate.isAuthorizedForLock( false, true, true, true ) ); } @Test @DisplayName("seat not lockable → denied") void seatNotLockable() { assertFalse( FurnitureAuthPredicate.isAuthorizedForLock( true, false, true, true ) ); } @Test @DisplayName("seat not occupied → denied") void seatNotOccupied() { assertFalse( FurnitureAuthPredicate.isAuthorizedForLock( true, true, false, true ) ); } @Test @DisplayName("occupant has no collar (or sender not owner) → denied") void noOwnedCollar() { assertFalse( FurnitureAuthPredicate.isAuthorizedForLock( true, true, true, false ) ); } @Test @DisplayName("all gates false → denied") void allFalse() { assertFalse( FurnitureAuthPredicate.isAuthorizedForLock( false, false, false, false ) ); } } @Nested @DisplayName("isAuthorizedForForceMount — AND of 5 gates") class ForceMountAuthorization { @Test @DisplayName("all five gates true → authorized") void allTrue() { assertTrue( FurnitureAuthPredicate.isAuthorizedForForceMount( true, true, true, true, true ) ); } @Test @DisplayName("captive not alive → denied") void captiveDead() { assertFalse( FurnitureAuthPredicate.isAuthorizedForForceMount( false, true, true, true, true ) ); } @Test @DisplayName("captive out of range → denied") void captiveOutOfRange() { assertFalse( FurnitureAuthPredicate.isAuthorizedForForceMount( true, false, true, true, true ) ); } @Test @DisplayName("no owned collar → denied") void noOwnedCollar() { assertFalse( FurnitureAuthPredicate.isAuthorizedForForceMount( true, true, false, true, true ) ); } @Test @DisplayName("captive not tied up (leashed) → denied (audit BUG-003)") void notTiedUp() { // This is the exact regression guard for audit BUG-003: before the // fix, the packet path did NOT check isTiedUp, so any collar-owning // captor could force-mount a captive that had never been leashed. assertFalse( FurnitureAuthPredicate.isAuthorizedForForceMount( true, true, true, false, true ) ); } @Test @DisplayName("captive already passenger of another entity → denied (audit BUG-003)") void alreadyPassenger() { // BUG-003 regression guard: before the fix, the packet path used // startRiding(force=true) which would break prior mounts silently. assertFalse( FurnitureAuthPredicate.isAuthorizedForForceMount( true, true, true, true, false ) ); } @Test @DisplayName("all gates false → denied") void allFalse() { assertFalse( FurnitureAuthPredicate.isAuthorizedForForceMount( false, false, false, false, false ) ); } } }