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()) {
|
||||
stateRecord.saveEnabled = true
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -43,18 +44,18 @@ object StateCenter {
|
||||
return null
|
||||
}
|
||||
|
||||
stateRecord.loaded = true
|
||||
stateRecord.saveEnabled = true
|
||||
|
||||
return JSONObject.parseObject(finalStatePath.readText())
|
||||
}
|
||||
|
||||
fun load(path: Path) {
|
||||
val finalStatePath = ConfigCenter.paths.stateDir.normalize().resolve(path).normalize()
|
||||
if (!stateRegistry.containsKey(path)) {
|
||||
if (!stateRegistry.containsKey(finalStatePath)) {
|
||||
return
|
||||
}
|
||||
val record = stateRegistry[finalStatePath] ?: return
|
||||
record.loaded = true
|
||||
record.saveEnabled = true
|
||||
if (!finalStatePath.exists()) {
|
||||
return
|
||||
}
|
||||
@@ -68,7 +69,7 @@ object StateCenter {
|
||||
|
||||
fun save() {
|
||||
stateRegistry.forEach { (path, record) ->
|
||||
if (!record.loaded) {
|
||||
if (!record.saveEnabled) {
|
||||
return@forEach
|
||||
}
|
||||
path.parent?.let(Files::createDirectories)
|
||||
@@ -156,5 +157,5 @@ sealed interface StateValue {
|
||||
|
||||
data class StateRecord(
|
||||
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