mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 04:53:00 +08:00
123 lines
4.6 KiB
Go
123 lines
4.6 KiB
Go
//go:build unix
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/matthewlu070111/smart-srun/core/internal/application"
|
|
"github.com/matthewlu070111/smart-srun/core/internal/config"
|
|
"github.com/matthewlu070111/smart-srun/core/internal/domain"
|
|
)
|
|
|
|
type switchRunner struct{}
|
|
|
|
func (switchRunner) Run(_ context.Context, a application.Action, _ func(application.Phase)) application.Outcome {
|
|
if a.Request.IdempotencyKey == "reject" {
|
|
return application.Outcome{State: application.StateFailed, Code: domain.CodeAuthRejected, Message: "rejected"}
|
|
}
|
|
return application.Outcome{State: application.StateSucceeded, Message: "connected"}
|
|
}
|
|
|
|
func setupSwitchRPC(t *testing.T, configure ...func(*Options)) *running {
|
|
r := start(t, func(o *Options) {
|
|
o.Runner = switchRunner{}
|
|
for _, apply := range configure {
|
|
apply(o)
|
|
}
|
|
})
|
|
for i := 0; i < 2; i++ {
|
|
r.writeConfig("campus.upsert", fmt.Sprintf(`{"expected_revision":%d,"account":{"user_id":"student%d","wired_iface":"wan"}}`, i, i))
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
r.writeConfig("hotspot.upsert", fmt.Sprintf(`{"expected_revision":%d,"profile":{"ssid":"hotspot%d","radio":"radio1","encryption":"none"}}`, 2+i, i))
|
|
}
|
|
return r
|
|
}
|
|
|
|
func TestSwitchRPCPersistsSelectionWithoutChangingDefaultsOrEnabled(t *testing.T) {
|
|
r := setupSwitchRPC(t)
|
|
var revision uint64 = 4
|
|
for _, target := range []struct{ kind, account, hotspot string }{{"switch_campus", "c2", ""}, {"switch_hotspot", "", "h2"}} {
|
|
params := SubmitParams{Kind: target.kind, AccountID: target.account, HotspotID: target.hotspot,
|
|
ExpectedRevision: &revision, IdempotencyKey: target.kind}
|
|
var receipt SubmitResult
|
|
if err := json.Unmarshal(r.call("action.submit", params), &receipt); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := r.awaitTerminal(receipt.ActionID); got.State != application.StateSucceeded {
|
|
t.Fatalf("switch failed: %+v", got)
|
|
}
|
|
var retry SubmitResult
|
|
if err := json.Unmarshal(r.call("action.submit", params), &retry); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !retry.Duplicate || retry.ActionID != receipt.ActionID {
|
|
t.Fatal("retry started another switch after commit")
|
|
}
|
|
revision++
|
|
}
|
|
loaded, err := config.LoadFile(r.paths.ConfigFile())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if loaded.Revision != 6 || loaded.Enabled || loaded.Selection != (domain.Selection{ActiveCampusID: "c2", DefaultCampusID: "c1", ActiveHotspotID: "h2", DefaultHotspotID: "h1"}) {
|
|
t.Fatalf("wrong durable choice: %+v", loaded.Selection)
|
|
}
|
|
for _, key := range []string{"same-target", "reject"} {
|
|
var receipt SubmitResult
|
|
json.Unmarshal(r.call("action.submit", SubmitParams{Kind: "switch_campus", AccountID: "c2", ExpectedRevision: &revision, IdempotencyKey: key}), &receipt)
|
|
got := r.awaitTerminal(receipt.ActionID)
|
|
if key == "reject" && got.State != application.StateFailed {
|
|
t.Fatal("failure was changed to success")
|
|
}
|
|
}
|
|
loaded, err = config.LoadFile(r.paths.ConfigFile())
|
|
if err != nil || loaded.Revision != 6 {
|
|
t.Fatal("same target or failed action wrote config", err)
|
|
}
|
|
// A new process opens precisely the saved selection, with no replay needed.
|
|
reopened, err := config.Open(r.paths.ConfigFile())
|
|
if err != nil || reopened.Snapshot().Selection != loaded.Selection {
|
|
t.Fatal("selection did not survive reopen", err)
|
|
}
|
|
}
|
|
|
|
func TestSwitchRPCReportsRecoveryWhenSelectionCannotBeSaved(t *testing.T) {
|
|
faults := make(chan error, 1)
|
|
worker := blockingRunner{entered: make(chan struct{}, 1), release: make(chan struct{})}
|
|
r := setupSwitchRPC(t, func(o *Options) {
|
|
o.OnError = func(err error) { faults <- err }
|
|
o.Runner = worker
|
|
})
|
|
var receipt SubmitResult
|
|
json.Unmarshal(r.call("action.submit", SubmitParams{Kind: "switch_campus", AccountID: "c2", IdempotencyKey: "save-failure"}), &receipt)
|
|
<-worker.entered
|
|
// Fail persistence after dispatch. An already unreadable recovery parent
|
|
// is now correctly refused by the updater guard before a switch begins.
|
|
backup := r.paths.Config + "-saved"
|
|
if err := os.Rename(r.paths.Config, backup); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(r.paths.Config, []byte("block config directory"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
close(worker.release)
|
|
got := r.awaitTerminal(receipt.ActionID)
|
|
if got.State != application.StateFailed || got.Code != domain.CodeRecoveryRequired {
|
|
t.Fatalf("save failure reported success: %+v", got)
|
|
}
|
|
if err := <-faults; err == nil {
|
|
t.Fatal("write failure was not reported")
|
|
}
|
|
var cfg domain.Config
|
|
json.Unmarshal(r.call("config.get", nil), &cfg)
|
|
if cfg.Selection.ActiveCampusID != "c1" || cfg.Revision != 4 {
|
|
t.Fatal("failed save published a new selection")
|
|
}
|
|
}
|