mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 08:43:02 +08:00
fix(state): enable first-time state file save and block overwrites until explicit load
This commit is contained in:
@@ -33,6 +33,7 @@ object StateCenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!finalStatePath.exists()) {
|
if (!finalStatePath.exists()) {
|
||||||
|
stateRecord.saveEnabled = true
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,18 +44,18 @@ object StateCenter {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
stateRecord.loaded = true
|
stateRecord.saveEnabled = true
|
||||||
|
|
||||||
return JSONObject.parseObject(finalStatePath.readText())
|
return JSONObject.parseObject(finalStatePath.readText())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(path: Path) {
|
fun load(path: Path) {
|
||||||
val finalStatePath = ConfigCenter.paths.stateDir.normalize().resolve(path).normalize()
|
val finalStatePath = ConfigCenter.paths.stateDir.normalize().resolve(path).normalize()
|
||||||
if (!stateRegistry.containsKey(path)) {
|
if (!stateRegistry.containsKey(finalStatePath)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val record = stateRegistry[finalStatePath] ?: return
|
val record = stateRegistry[finalStatePath] ?: return
|
||||||
record.loaded = true
|
record.saveEnabled = true
|
||||||
if (!finalStatePath.exists()) {
|
if (!finalStatePath.exists()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -68,7 +69,7 @@ object StateCenter {
|
|||||||
|
|
||||||
fun save() {
|
fun save() {
|
||||||
stateRegistry.forEach { (path, record) ->
|
stateRegistry.forEach { (path, record) ->
|
||||||
if (!record.loaded) {
|
if (!record.saveEnabled) {
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
path.parent?.let(Files::createDirectories)
|
path.parent?.let(Files::createDirectories)
|
||||||
@@ -156,5 +157,5 @@ sealed interface StateValue {
|
|||||||
|
|
||||||
data class StateRecord(
|
data class StateRecord(
|
||||||
val serializable: StateSerializable,
|
val serializable: StateSerializable,
|
||||||
var loaded: Boolean = false
|
var saveEnabled: Boolean = false
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package work.slhaf.partner.framework.agent.state;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import work.slhaf.partner.framework.agent.config.ConfigCenter;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class StateCenterTest {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static void clearRegistry() throws Exception {
|
||||||
|
Field registryField = StateCenter.class.getDeclaredField("stateRegistry");
|
||||||
|
registryField.setAccessible(true);
|
||||||
|
ConcurrentHashMap<Path, StateRecord> registry = (ConcurrentHashMap<Path, StateRecord>) registryField.get(StateCenter.INSTANCE);
|
||||||
|
registry.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() throws Exception {
|
||||||
|
clearRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateStateFileOnFirstSaveWhenNoExistingFile() throws Exception {
|
||||||
|
Path relativePath = Path.of("state-center-test", UUID.randomUUID() + ".json");
|
||||||
|
Path finalPath = ConfigCenter.INSTANCE.getPaths().getStateDir().resolve(relativePath).normalize();
|
||||||
|
Files.deleteIfExists(finalPath);
|
||||||
|
|
||||||
|
StubStateSerializable serializable = new StubStateSerializable(relativePath, false, "fresh");
|
||||||
|
serializable.register();
|
||||||
|
StateCenter.INSTANCE.save();
|
||||||
|
|
||||||
|
assertTrue(Files.exists(finalPath));
|
||||||
|
JSONObject saved = JSONObject.parseObject(Files.readString(finalPath, StandardCharsets.UTF_8));
|
||||||
|
assertEquals("fresh", saved.getString("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotOverwriteExistingFileUntilManualLoadWhenAutoLoadDisabled() throws Exception {
|
||||||
|
Path relativePath = Path.of("state-center-test", UUID.randomUUID() + ".json");
|
||||||
|
Path finalPath = ConfigCenter.INSTANCE.getPaths().getStateDir().resolve(relativePath).normalize();
|
||||||
|
Files.createDirectories(finalPath.getParent());
|
||||||
|
Files.writeString(finalPath, "{\"value\":\"original\"}", StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
StubStateSerializable serializable = new StubStateSerializable(relativePath, false, "new-value");
|
||||||
|
serializable.register();
|
||||||
|
StateCenter.INSTANCE.save();
|
||||||
|
|
||||||
|
JSONObject untouched = JSONObject.parseObject(Files.readString(finalPath, StandardCharsets.UTF_8));
|
||||||
|
assertEquals("original", untouched.getString("value"));
|
||||||
|
|
||||||
|
serializable.load();
|
||||||
|
serializable.value = "after-load";
|
||||||
|
StateCenter.INSTANCE.save();
|
||||||
|
|
||||||
|
JSONObject saved = JSONObject.parseObject(Files.readString(finalPath, StandardCharsets.UTF_8));
|
||||||
|
assertEquals("after-load", saved.getString("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StubStateSerializable implements StateSerializable {
|
||||||
|
private final Path statePath;
|
||||||
|
private final boolean autoLoadOnRegister;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
private StubStateSerializable(Path statePath, boolean autoLoadOnRegister, String value) {
|
||||||
|
this.statePath = statePath;
|
||||||
|
this.autoLoadOnRegister = autoLoadOnRegister;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Path statePath() {
|
||||||
|
return statePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load(JSONObject state) {
|
||||||
|
value = state.getString("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull State convert() {
|
||||||
|
State state = new State();
|
||||||
|
state.append("value", StateValue.Companion.str(value));
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoLoadOnRegister() {
|
||||||
|
return autoLoadOnRegister;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user