Rehydrate default loop on empty snapshot

This commit is contained in:
Jacob Dubin
2026-05-19 20:06:03 -05:00
parent 30493d554b
commit b172a00454
11 changed files with 45355 additions and 3 deletions

View File

@@ -170,6 +170,8 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
_people.Clear();
_people.AddRange(snapshot.People ?? []);
EnsureDefaultTopology();
if (_robotProfile is null ||
!string.Equals(_robotProfile.RobotId, _robot.RobotId, StringComparison.OrdinalIgnoreCase))
_robotProfile = new RobotProfile
@@ -640,6 +642,46 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
SavePersistedState();
}
private void EnsureDefaultTopology()
{
if (_loops.Count == 0)
{
_loops.Add(new LoopRecord
{
OwnerAccountId = _account.AccountId,
RobotId = _robot.RobotId,
RobotFriendlyId = _robot.DeviceId
});
}
if (_people.Count != 0)
{
return;
}
var loopId = _loops[0].LoopId;
_people.Add(new PersonRecord
{
PersonId = "person-openjibo-owner",
AccountId = _account.AccountId,
LoopId = loopId,
RobotId = _robot.RobotId,
DisplayName = $"{_account.FirstName} {_account.LastName}",
Alias = _account.FirstName,
IsPrimary = true
});
_people.Add(new PersonRecord
{
PersonId = "person-openjibo-household-member",
AccountId = _account.AccountId,
LoopId = loopId,
RobotId = _robot.RobotId,
DisplayName = "OpenJibo Household Member",
Alias = "Household Member",
IsPrimary = false
});
}
private static string Slugify(string value)
{
var builder = new StringBuilder(value.Length);

View File

@@ -354,7 +354,7 @@
}
function getLoopId(parsed) {
return parsed?.loopId || parsed?.id || state.loops[0].id;
return parsed?.loopId || parsed?.id || state.loops[0]?.id || "fake-loop-id";
}
function getOrCreateSymmetricKey(loopId) {
@@ -587,10 +587,24 @@
console.log("LOOP OPERATION:", operation);
if (operation === "List" || operation === "ListLoops") {
const loops = state.loops.length > 0
? state.loops
: [{
id: "fake-loop-id",
name: "OpenJibo Test Loop",
owner: "usr_test_001",
robot: "my-robot-name",
robotFriendlyId: "my-robot-serial-number",
members: [],
isSuspended: false,
created: Date.now(),
updated: Date.now()
}];
return {
statusCode: 200,
note: "Returned one loop",
body: state.loops
body: loops
};
}

View File

@@ -128,6 +128,33 @@ public sealed class PersistenceStoreTests
}
}
[Fact]
public void CloudStateStore_RehydratesDefaultLoopWhenSnapshotLoopsAreMissing()
{
var persistencePath = Path.Combine(Path.GetTempPath(), $"openjibo-cloud-empty-loops-{Guid.NewGuid():N}.json");
try
{
File.WriteAllText(persistencePath, """
{
"SchemaVersion": "1",
"Revision": 7,
"Loops": []
}
""");
var store = new InMemoryCloudStateStore(persistencePath);
Assert.NotEmpty(store.GetLoops());
Assert.Equal("openjibo-default-loop", store.GetLoops()[0].LoopId);
Assert.NotEmpty(store.GetPeople());
}
finally
{
if (File.Exists(persistencePath)) File.Delete(persistencePath);
}
}
private sealed class RecordingSnapshotStore : ISnapshotStore
{
public List<object> Saves { get; } = [];
@@ -142,4 +169,4 @@ public sealed class PersistenceStoreTests
Saves.Add(snapshot);
}
}
}
}