Implement update backup and restore proof
This commit is contained in:
@@ -891,7 +891,7 @@ For `1.0.19`:
|
||||
- store contracts are now tightened around account/loop/device/person scoping, revision tracking, and explicit load/save boundaries
|
||||
- the backend seam is now selectable, with file-backed local persistence as default and an Azure Blob Storage slot wired for future deployment when a storage account connection string is available
|
||||
- next implementation pass should supply the real Azure Storage connection string / deployment wiring and validate the live round-trip in the storage account smoke test
|
||||
10. Update, backup, and restore proof
|
||||
10. Update, backup, and restore proof - implemented (update creation and backup creation now survive persisted reloads; restore proof uses the same persisted state rehydration path)
|
||||
11. STT upgrade and noise screening
|
||||
12. Hosted capture/storage plan / indexing for group testing
|
||||
13. Binary-safe media storage / sync to cloud drive: OneDrive, Google Drive, Box, etc.
|
||||
|
||||
@@ -338,7 +338,7 @@ First completed slice in this personal-report parity track:
|
||||
4. Personal report parity slices
|
||||
5. Holidays and seasonal personality slice beyond pizza day
|
||||
6. Durable memory persistence path
|
||||
7. Update/backup/restore end-to-end proof
|
||||
7. Update/backup/restore end-to-end proof - implemented
|
||||
8. STT noise-screening and short-utterance reliability pass
|
||||
9. Provider-backed news expansion and deeper weather parity
|
||||
10. Capture indexing and retention boundary for group testing
|
||||
|
||||
@@ -35,6 +35,7 @@ public interface ICloudStateStore
|
||||
IDictionary<string, object?>? meta);
|
||||
|
||||
IReadOnlyList<BackupRecord> GetBackups();
|
||||
BackupRecord CreateBackup(string name);
|
||||
bool ShouldCreateSymmetricKey(string loopId);
|
||||
string GetOrCreateSymmetricKey(string loopId);
|
||||
KeyRequestRecord CreateKeyRequest(string loopId, string publicKey);
|
||||
@@ -43,4 +44,4 @@ public interface ICloudStateStore
|
||||
IReadOnlyList<KeyRequestRecord> GetBinaryRequests();
|
||||
IReadOnlyList<object> GetHolidays();
|
||||
void UpdateRobot(DeviceRegistration registration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
||||
return Task.FromResult(HandleLog(operation, envelope));
|
||||
|
||||
if (servicePrefix.StartsWith("Backup_", StringComparison.OrdinalIgnoreCase))
|
||||
return Task.FromResult(HandleBackup(operation));
|
||||
return Task.FromResult(HandleBackup(operation, envelope));
|
||||
|
||||
if (servicePrefix.StartsWith("Account_", StringComparison.OrdinalIgnoreCase))
|
||||
return Task.FromResult(HandleAccount(operation, envelope));
|
||||
@@ -353,11 +353,19 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
||||
: []);
|
||||
}
|
||||
|
||||
private ProtocolDispatchResult HandleBackup(string operation)
|
||||
private ProtocolDispatchResult HandleBackup(string operation, ProtocolEnvelope envelope)
|
||||
{
|
||||
return operation.Equals("List", StringComparison.OrdinalIgnoreCase)
|
||||
? ProtocolDispatchResult.Ok(stateStore.GetBackups())
|
||||
: ProtocolDispatchResult.Ok(Array.Empty<object>());
|
||||
if (operation.Equals("List", StringComparison.OrdinalIgnoreCase))
|
||||
return ProtocolDispatchResult.Ok(stateStore.GetBackups());
|
||||
|
||||
if (operation.Equals("Create", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var body = envelope.TryParseBody();
|
||||
var requestedName = ReadString(body, "name") ?? ReadString(body, "backupName");
|
||||
return ProtocolDispatchResult.Ok(stateStore.CreateBackup(requestedName ?? $"backup-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}"));
|
||||
}
|
||||
|
||||
return ProtocolDispatchResult.Ok(Array.Empty<object>());
|
||||
}
|
||||
|
||||
private ProtocolDispatchResult HandleKey(string operation, ProtocolEnvelope envelope)
|
||||
@@ -631,4 +639,4 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
||||
bool.TryParse(value, out var parsed) &&
|
||||
parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,6 +452,18 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
|
||||
return _backups.ToArray();
|
||||
}
|
||||
|
||||
public BackupRecord CreateBackup(string name)
|
||||
{
|
||||
var backup = new BackupRecord
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(name) ? "backup" : name.Trim()
|
||||
};
|
||||
|
||||
_backups.Add(backup);
|
||||
TouchState();
|
||||
return backup;
|
||||
}
|
||||
|
||||
public bool ShouldCreateSymmetricKey(string loopId)
|
||||
{
|
||||
return !_symmetricKeys.ContainsKey(loopId);
|
||||
@@ -661,4 +673,4 @@ public sealed class InMemoryCloudStateStore : ICloudStateStore
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,69 @@ public sealed class JiboCloudProtocolServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAndBackupPersistAcrossStoreRecreation_WhenPersistencePathIsConfigured()
|
||||
{
|
||||
var persistencePath = Path.Combine(Path.GetTempPath(), $"openjibo-update-backup-{Guid.NewGuid():N}.json");
|
||||
try
|
||||
{
|
||||
var firstService = new JiboCloudProtocolService(new InMemoryCloudStateStore(persistencePath));
|
||||
|
||||
await firstService.DispatchAsync(new ProtocolEnvelope
|
||||
{
|
||||
HostName = "api.jibo.com",
|
||||
Method = "POST",
|
||||
ServicePrefix = "Update_20160715",
|
||||
Operation = "CreateUpdate",
|
||||
BodyText = """{"fromVersion":"1.0.0","toVersion":"1.0.1","changes":"Restore proof","subsystem":"robot"}"""
|
||||
});
|
||||
|
||||
await firstService.DispatchAsync(new ProtocolEnvelope
|
||||
{
|
||||
HostName = "api.jibo.com",
|
||||
Method = "POST",
|
||||
ServicePrefix = "Backup_20160715",
|
||||
Operation = "Create",
|
||||
BodyText = """{"name":"manual-backup"}"""
|
||||
});
|
||||
|
||||
var secondService = new JiboCloudProtocolService(new InMemoryCloudStateStore(persistencePath));
|
||||
|
||||
var updates = await secondService.DispatchAsync(new ProtocolEnvelope
|
||||
{
|
||||
HostName = "api.jibo.com",
|
||||
Method = "POST",
|
||||
ServicePrefix = "Update_20160715",
|
||||
Operation = "ListUpdates",
|
||||
BodyText = """{"subsystem":"robot"}"""
|
||||
});
|
||||
|
||||
var backups = await secondService.DispatchAsync(new ProtocolEnvelope
|
||||
{
|
||||
HostName = "api.jibo.com",
|
||||
Method = "POST",
|
||||
ServicePrefix = "Backup_20160715",
|
||||
Operation = "List",
|
||||
BodyText = "{}"
|
||||
});
|
||||
|
||||
using var updatesPayload = JsonDocument.Parse(updates.BodyText);
|
||||
using var backupsPayload = JsonDocument.Parse(backups.BodyText);
|
||||
|
||||
Assert.NotEmpty(updatesPayload.RootElement.EnumerateArray());
|
||||
Assert.Contains(updatesPayload.RootElement.EnumerateArray(), item => item.GetProperty("changes").GetString() == "Restore proof");
|
||||
Assert.NotEmpty(backupsPayload.RootElement.EnumerateArray());
|
||||
Assert.Contains(backupsPayload.RootElement.EnumerateArray(), item => item.TryGetProperty("Name", out var name) && name.GetString() == "manual-backup");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(persistencePath))
|
||||
{
|
||||
File.Delete(persistencePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MediaCreate_StoresBodyAndServesMediaUrl()
|
||||
{
|
||||
@@ -230,4 +293,4 @@ public sealed class JiboCloudProtocolServiceTests
|
||||
Assert.Contains(people,
|
||||
person => string.Equals(person.LoopId, store.GetLoops()[0].LoopId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user