mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-09-07 15:07:17 +00:00
feat(setup): report migration data readiness in the summary
The readiness verdict was only reachable by attempting the migration, so the UI's pre-flight panel could show every check green and then fail at Apply with a 409. The user commits to the operation before learning it will be refused. GetMigrationSummary now runs the same read-only check and reports it: data_ready_error carries the reason migration would be refused, and data_ready_warnings carries the advisory ones. Nothing is enforced here, and MigrateSpeaker still runs the check itself, so this cannot let a refused migration through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b43fb05ea1
commit
ad65058354
@@ -331,3 +331,48 @@ func TestMigrationDataReadinessExplainsPresetsDroppedByFull(t *testing.T) {
|
||||
t.Errorf("action = %q, want it to point at the missing source", notReady.Action)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMigrationSummaryReportsDataReadiness: the pre-flight panel must show a
|
||||
// refusal before the user commits, rather than showing all-green and failing
|
||||
// with a 409 at Apply.
|
||||
func TestMigrationSummaryReportsDataReadiness(t *testing.T) {
|
||||
t.Run("refusal", func(t *testing.T) {
|
||||
m, _, deviceIP := newMigrationReadinessFixture(t, `<presets/>`)
|
||||
|
||||
// No persisted snapshot, so the check refuses.
|
||||
summary, err := m.GetMigrationSummary(deviceIP, "http://aftertouch.example:8000", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMigrationSummary: %v", err)
|
||||
}
|
||||
|
||||
if summary.DataReadyError == "" {
|
||||
t.Error("summary hid a refusal that Apply would hit")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("warning", func(t *testing.T) {
|
||||
m, ds, deviceIP := newMigrationReadinessFixture(t, `<presets/>`)
|
||||
if err := ds.SavePresets(readinessAccount, readinessDevice, nil); err != nil {
|
||||
t.Fatalf("SavePresets: %v", err)
|
||||
}
|
||||
if err := ds.SaveDeviceInfo(readinessAccount, "SIBLING01", &models.ServiceDeviceInfo{
|
||||
DeviceID: "SIBLING01",
|
||||
AccountID: readinessAccount,
|
||||
Name: "Sibling Speaker",
|
||||
}); err != nil {
|
||||
t.Fatalf("SaveDeviceInfo sibling: %v", err)
|
||||
}
|
||||
|
||||
summary, err := m.GetMigrationSummary(deviceIP, "http://aftertouch.example:8000", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMigrationSummary: %v", err)
|
||||
}
|
||||
|
||||
if summary.DataReadyError != "" {
|
||||
t.Errorf("summary reported a refusal for a shared account: %q", summary.DataReadyError)
|
||||
}
|
||||
if len(summary.DataReadyWarnings) != 1 {
|
||||
t.Errorf("warnings = %v, want one about the device count", summary.DataReadyWarnings)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,6 +80,12 @@ type MigrationSummary struct {
|
||||
CurrentResolvConf string `json:"current_resolv_conf,omitempty"`
|
||||
PlannedResolv string `json:"planned_resolv,omitempty"`
|
||||
IsMigrated bool `json:"is_migrated"`
|
||||
// Data readiness, so the pre-flight panel can show a refusal before the
|
||||
// user commits to Apply instead of only surfacing it as a 409 afterwards.
|
||||
// DataReadyError is the reason migration will be refused; empty means it
|
||||
// will proceed. DataReadyWarnings are advisory and do not block.
|
||||
DataReadyError string `json:"data_ready_error,omitempty"`
|
||||
DataReadyWarnings []string `json:"data_ready_warnings,omitempty"`
|
||||
// Per-axis migration signals — IsMigrated is the OR of these. The UI
|
||||
// displays them individually so users can see partial states (e.g.
|
||||
// URLs flipped via telnet but the on-disk XML hasn't caught up, or
|
||||
@@ -303,6 +309,13 @@ func (m *Manager) GetMigrationSummary(deviceIP, targetURL, proxyURL string, opti
|
||||
SSHSuccess: false,
|
||||
}
|
||||
|
||||
// Same read-only check MigrateSpeaker runs, reported rather than enforced.
|
||||
if warnings, err := m.checkMigrationDataReady(deviceIP); err != nil {
|
||||
summary.DataReadyError = err.Error()
|
||||
} else {
|
||||
summary.DataReadyWarnings = warnings
|
||||
}
|
||||
|
||||
// Run the telnet preflight in parallel with the SSH-based probes below.
|
||||
// Both transports are queried independently: SSH gives access to
|
||||
// /etc/hosts, /etc/resolv.conf and the on-device XML config; telnet's
|
||||
|
||||
Reference in New Issue
Block a user