test(revival): fence administrator audit export observations
CI / build (push) Successful in 7m45s
CI / build (pull_request) Successful in 7m52s

This commit is contained in:
dmg committed 2026-10-07 18:52:03 -04:00
1 parent 6dc706b6ff
commit f1b3fc5094
1 file changed
+141 -18
@@ -61,7 +61,6 @@ class AdminActionIdentityAndEquipmentNativeTest {
assertEquals(recipient.core.id.toString(), value(issue.getFirst(), "target_player_id"));
assertEquals(slot, issue.getFirst().getAsJsonObject("data").get("delivered_slot").getAsInt());
assertEquals("native_inventory_acknowledged", value(issue.getFirst(), "delivery_outcome"));
settle(f);
var rows = actions(data);
sequence(rows, kind, "give", "completed");
linked(rows, value(issue.getFirst(), "issue_operation_id"));
@@ -101,7 +100,6 @@ class AdminActionIdentityAndEquipmentNativeTest {
assertEquals(after.id().toString(), value(grant, "target_character_id"));
assertEquals(after.experience().toString(), value(grant, "after_xp"));
assertEquals("250", value(grant, "requested_xp"));
settle(f);
var rows = actions(data);
sequence(rows, kind, "experience", "completed");
linked(rows, value(grant, "operation_id"));
@@ -133,7 +131,6 @@ class AdminActionIdentityAndEquipmentNativeTest {
assertEquals("pending", value(inspected.getFirst(), "receipt_phase"));
assertEquals(pending.sessionId().toString(), value(inspected.getFirst(), "session_id"));
assertEquals("packed", value(inspected.getFirst(), "native_phase"));
settle(f);
var rows = actions(data);
sequence(rows, kind, "inspect", "completed");
assertEquals(target.core.id.toString(), rows.getFirst().getAsJsonObject("data")
@@ -159,7 +156,7 @@ class AdminActionIdentityAndEquipmentNativeTest {
assertNotNull(retained.pending());
assertEquals(session, retained.pending().sessionId());
target.core.inventory.setItem(0, null);
int prior = actions(data).size();
int prior = actions(f, data).size();
assertTrue(entry.map.dispatch(entry.sender, "fantasystick:fstickadmin recover " + target.core.id));
ReskillEventsNativeTest.await(f, () -> {
try { return f.receipts().find(target.core.id).toCompletableFuture().get(5, TimeUnit.SECONDS)
@@ -179,7 +176,6 @@ class AdminActionIdentityAndEquipmentNativeTest {
assertEquals("verified_box_and_marker", value(resolved.getFirst(), "custody_quality"));
assertEquals(session.toString(), value(resolved.getFirst(), "session_id"));
assertNotNull(value(resolved.getFirst(), "equipment_recovery_operation_id"));
settle(f);
var rows = actions(data).subList(prior, actions(data).size());
sequence(rows, kind, "recover", "completed");
linked(rows, value(resolved.getFirst(), "recovery_operation_id"));
@@ -201,7 +197,7 @@ class AdminActionIdentityAndEquipmentNativeTest {
var stored = storage(f).inspect(source.core.record.id()).get(5, TimeUnit.SECONDS).orElseThrow();
var current = storage(f).inspect(target.core.record.id()).get(5, TimeUnit.SECONDS).orElseThrow();
var checks = new ArrayList<org.junit.jupiter.api.function.Executable>();
int prior = actions(data).size();
int prior = actions(f, data).size();
assertTrue(entry.map.dispatch(entry.sender, "fstickadmin character view CurrentHolder"));
ReskillEventsNativeTest.await(f, () -> !fixture.actor.books.isEmpty());
assertEquals(Material.WRITTEN_BOOK, fixture.actor.books.getLast().getType());
@@ -242,7 +238,7 @@ class AdminActionIdentityAndEquipmentNativeTest {
f.activate(target);
var saved = storage(f).inspect(target.core.record.id()).get(5, TimeUnit.SECONDS).orElseThrow();
var checks = new ArrayList<org.junit.jupiter.api.function.Executable>();
int before = actions(data).size();
int before = actions(f, data).size();
assertTrue(entry.map.dispatch(entry.sender, "fstickadmin character find " + saved.name()));
ReskillEventsNativeTest.await(f, () -> entry.messages().stream().anyMatch(m -> m.contains("Saved character results")));
assertTrue(entry.messages().stream().anyMatch(m -> m.contains(saved.id().toString())));
@@ -275,16 +271,13 @@ class AdminActionIdentityAndEquipmentNativeTest {
int messages = fixture.actor.messages.size();
assertTrue(entry.map.dispatch(entry.sender, "fstickadmin inspect " + target.core.id));
fixture.actor.allowed = false;
ReskillEventsNativeTest.await(f, () -> {
try { return domain(data, "action.resolved").stream()
.anyMatch(row -> "permission_changed".equals(value(row, "reason"))); }
catch (Exception error) { throw new AssertionError(error); }
});
awaitExport(f, () -> domain(data, "action.resolved").stream()
.anyMatch(row -> "permission_changed".equals(value(row, "reason"))));
assertEquals(messages, fixture.actor.messages.size());
assertArrayEquals(nativeBefore, target.core.snapshot());
settle(f);
assertTrue(domain(data, "recovery.inspected").isEmpty());
assertEquals("permission_changed", value(domain(data, "action.resolved").getLast(), "reason"));
settle(f);
var rows = actions(data);
sequence(rows, "player", "inspect", "refused");
assertFalse(rows.getLast().getAsJsonObject("data").has("subject_id"));
@@ -304,7 +297,7 @@ class AdminActionIdentityAndEquipmentNativeTest {
"experience give NotExact 42", "inspect " + fixture.actor.core.id.toString().substring(0, 8),
"recover bad-uuid", "character view NotExact", "character show " + saved.id().toString().substring(0, 8),
"character show " + UUID.randomUUID())) {
int offset = actions(data).size();
int offset = actions(f, data).size();
int feedback = fixture.actor.messages.size();
int books = fixture.actor.books.size();
assertTrue(entry.map.dispatch(entry.sender, "fstickadmin " + command));
@@ -328,6 +321,111 @@ class AdminActionIdentityAndEquipmentNativeTest {
}
}
@Test void issuedAuditObservationPumpsPendingNativeDeliveryBeforeExport() throws Exception {
try (var fixture = new QuestTrackingAuditNativeFixture(data)) {
var f = fixture.f;
var entry = new AdminActionEntryNativeTest.Entry(fixture, "command_block", true);
var recipient = actor(f, data, "Recipient");
int slot = recipient.core.inventory.firstEmpty();
assertTrue(entry.map.dispatch(entry.sender, "fantasystick:fstickadmin give Recipient"));
storage(f).find(UUID.randomUUID()).get(5, TimeUnit.SECONDS);
assertNull(recipient.core.inventory.getItem(slot), "Worker allocation still needs its native delivery callback");
assertTrue(observeExport(f, () -> domain(data, "character.issued").isEmpty(), TimeUnit.SECONDS.toNanos(5)),
"No success export exists before native delivery");
awaitDomain(f, data, "character.issued", 1);
var sheets = new CharacterSheets();
var item = recipient.core.inventory.getItem(slot);
var issued = sheets.identify(item).orElseThrow();
assertTrue(sheets.authorizes(item, storage(f).find(issued).get(5, TimeUnit.SECONDS).orElseThrow()));
var issue = domain(data, "character.issued");
assertEquals(1, issue.size());
assertEquals(issued.toString(), value(issue.getFirst(), "character_id"));
assertEquals(recipient.core.id.toString(), value(issue.getFirst(), "target_player_id"));
sequence(actions(data), "command_block", "give", "completed");
}
}
@Test void issuedAuditObservationWaitsForOwnedInFlightAppendBeforeStrictParsing() throws Exception {
try (var fixture = new QuestTrackingAuditNativeFixture(data)) {
var f = fixture.f;
var segment = issueAndSettle(fixture);
var complete = java.nio.file.Files.readAllBytes(segment);
var writer = (java.util.concurrent.ThreadPoolExecutor) FantasyStickLifecycleNativeTest.Fixture.field(
FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "eventLog"), "worker");
var partialVisible = new java.util.concurrent.CountDownLatch(1);
var release = new java.util.concurrent.CountDownLatch(1);
var returned = new java.util.concurrent.CountDownLatch(1);
var premature = new java.util.concurrent.atomic.AtomicBoolean();
// Replay visibility of the same genuine exported bytes, not a fabricated issue/result.
// Only the fixture-owned writer is active; completed bytes/counts must be unchanged.
var append = writer.submit(() -> {
try {
java.nio.file.Files.write(segment, java.util.Arrays.copyOf(complete, complete.length - 2));
partialVisible.countDown();
assertTrue(release.await(5, TimeUnit.SECONDS));
} catch (Exception error) { throw new AssertionError(error); }
finally {
try { java.nio.file.Files.write(segment, complete); }
catch (Exception error) { throw new AssertionError(error); }
}
});
java.util.concurrent.CompletableFuture<Void> observer = null;
try {
assertTrue(partialVisible.await(5, TimeUnit.SECONDS));
observer = java.util.concurrent.CompletableFuture.runAsync(() -> {
try { premature.set(returned.await(200, TimeUnit.MILLISECONDS)); }
catch (InterruptedException error) { Thread.currentThread().interrupt(); throw new AssertionError(error); }
finally { release.countDown(); }
});
awaitDomain(f, data, "character.issued", 1);
returned.countDown(); observer.get(5, TimeUnit.SECONDS);
assertFalse(premature.get(), "Audit observation must wait for its owned append, not a visible prefix");
} finally {
release.countDown();
try { if (observer != null) observer.get(5, TimeUnit.SECONDS); }
finally { try { append.get(5, TimeUnit.SECONDS); } finally { settle(f); } }
}
assertArrayEquals(complete, java.nio.file.Files.readAllBytes(segment));
assertEquals(1, domain(data, "character.issued").size());
sequence(actions(data), "command_block", "give", "completed");
}
}
@Test void completedMalformedIssuedAuditStillFailsStrictVerification() throws Exception {
try (var fixture = new QuestTrackingAuditNativeFixture(data)) {
var f = fixture.f;
var segment = issueAndSettle(fixture);
var complete = java.nio.file.Files.readAllBytes(segment);
try {
java.nio.file.Files.write(segment, java.util.Arrays.copyOf(complete, complete.length - 2));
assertTrue(FixtureAuditAcknowledgmentNativeTest.snapshot(f).idle());
var failure = assertThrows(AssertionError.class, () -> awaitDomain(f, data, "character.issued", 1));
assertInstanceOf(com.google.gson.JsonSyntaxException.class, failure.getCause(),
"Completed malformed export must not be skipped, swallowed or accepted as success");
} finally { java.nio.file.Files.write(segment, complete); }
assertEquals(1, domain(data, "character.issued").size());
sequence(actions(data), "command_block", "give", "completed");
}
}
private Path issueAndSettle(QuestTrackingAuditNativeFixture fixture) throws Exception {
var f = fixture.f;
var entry = new AdminActionEntryNativeTest.Entry(fixture, "command_block", true);
var recipient = actor(f, data, "Recipient");
int slot = recipient.core.inventory.firstEmpty();
assertTrue(entry.map.dispatch(entry.sender, "fantasystick:fstickadmin give Recipient"));
storage(f).find(UUID.randomUUID()).get(5, TimeUnit.SECONDS);
ReskillEventsNativeTest.await(f, () -> recipient.core.inventory.getItem(slot) != null);
assertTrue(new CharacterSheets().authorizes(recipient.core.inventory.getItem(slot),
storage(f).find(new CharacterSheets().identify(recipient.core.inventory.getItem(slot)).orElseThrow())
.get(5, TimeUnit.SECONDS).orElseThrow()));
settle(f);
assertEquals(1, domain(data, "character.issued").size());
sequence(actions(data), "command_block", "give", "completed");
return (Path) FantasyStickLifecycleNativeTest.Fixture.field(
FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "eventLog"), "segment");
}
private static FantasyStickLifecycleNativeTest.Actor actor(FantasyStickLifecycleNativeTest.Fixture f, Path root, String name) throws Exception {
var actor = new FantasyStickLifecycleNativeTest.Actor(root);
actor.accountName = name;
@@ -341,15 +439,40 @@ class AdminActionIdentityAndEquipmentNativeTest {
return (EquipmentSessions) FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "sessions");
}
private static List<JsonObject> actions(Path root) throws Exception { return AdminActionEntryNativeTest.rows(root); }
private static List<JsonObject> actions(FantasyStickLifecycleNativeTest.Fixture f, Path root) throws Exception {
return observeExport(f, () -> actions(root), TimeUnit.SECONDS.toNanos(5));
}
private static List<JsonObject> domain(Path root, String suffix) throws Exception {
return ReskillEventsNativeTest.records(root).stream().filter(row -> row.get("type").getAsString()
.equals("games.dmg.minecraft.fantasystick." + suffix)).toList();
}
private static void awaitDomain(FantasyStickLifecycleNativeTest.Fixture f, Path root, String suffix, int count) throws Exception {
ReskillEventsNativeTest.await(f, () -> {
try { return domain(root, suffix).size() >= count; }
catch (Exception error) { throw new AssertionError(error); }
});
awaitExport(f, () -> domain(root, suffix).size() >= count);
}
private static <T> T observeExport(FantasyStickLifecycleNativeTest.Fixture f,
java.util.concurrent.Callable<T> read, long remaining) throws Exception {
var writer = (java.util.concurrent.ThreadPoolExecutor) FantasyStickLifecycleNativeTest.Fixture.field(
FantasyStickLifecycleNativeTest.Fixture.field(f.plugin, "eventLog"), "worker");
try { return writer.submit(read).get(remaining, TimeUnit.NANOSECONDS); }
catch (java.util.concurrent.ExecutionException error) { throw new AssertionError(error.getCause()); }
}
private static void awaitExport(FantasyStickLifecycleNativeTest.Fixture f,
java.util.concurrent.Callable<Boolean> observed) throws Exception {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
while (true) {
long remaining = deadline - System.nanoTime();
assertTrue(remaining > 0, "Expected audit export did not arrive within its existing five-second bound");
// Strict file I/O shares the actual owned serial writer: never parse mid-append.
// Missing rows may await native callbacks; malformed completed rows still fail.
if (observeExport(f, observed, remaining)) {
settle(f); // Relocated existing acknowledgment fence before subsequent strict assertions.
return;
}
remaining = deadline - System.nanoTime();
assertTrue(remaining > 0, "Expected audit export did not arrive within its existing five-second bound");
var task = f.tasks.poll(Math.min(remaining, TimeUnit.MILLISECONDS.toNanos(50)), TimeUnit.NANOSECONDS);
if (task != null) task.run();
}
}
private static void settle(FantasyStickLifecycleNativeTest.Fixture f) throws Exception {
FixtureAuditAcknowledgmentNativeTest.awaitOwnQuiescence(f);