99 lines
3.3 KiB
Go
99 lines
3.3 KiB
Go
//go:build linux
|
|
|
|
package adminconsole
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
|
|
"git.pcux.cn/xuans/Scert/backend/internal/localreset"
|
|
)
|
|
|
|
type consoleIPCSession struct {
|
|
selected atomic.Int32
|
|
verified atomic.Int32
|
|
resets atomic.Int32
|
|
inspected atomic.Int32
|
|
}
|
|
|
|
func (*consoleIPCSession) Methods() []localreset.Method {
|
|
return []localreset.Method{{ID: "email", Label: "绑定邮箱验证码"}}
|
|
}
|
|
func (s *consoleIPCSession) Select(context.Context, string) error { s.selected.Add(1); return nil }
|
|
func (s *consoleIPCSession) Verify(context.Context, string) error { s.verified.Add(1); return nil }
|
|
func (s *consoleIPCSession) Reset(context.Context, string) error { s.resets.Add(1); return nil }
|
|
func (*consoleIPCSession) Close() {}
|
|
func (*consoleIPCSession) RecoveryAvailable() bool { return true }
|
|
func (*consoleIPCSession) ListRecoveries(context.Context, string) (deployment.PendingRecoveryPage, error) {
|
|
return deployment.PendingRecoveryPage{Items: []deployment.PendingRecovery{{ID: "tx-1", ServerID: "server-1"}}}, nil
|
|
}
|
|
func (s *consoleIPCSession) InspectRecovery(_ context.Context, id string) (deployment.RecoveryInspection, error) {
|
|
s.inspected.Add(1)
|
|
return deployment.RecoveryInspection{TransactionID: id, ServerID: "server-1", ObservedAt: time.Now(),
|
|
Files: make([]deployment.RecoveryFileObservation, 9), Locks: make([]deployment.RecoveryLockObservation, 3)}, nil
|
|
}
|
|
|
|
func TestRecoveryConsoleOverProtectedUnixSocket(t *testing.T) {
|
|
for _, action := range []string{"inspect", "reset", "quit"} {
|
|
t.Run(action, func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "control.sock")
|
|
server, err := localreset.Listen(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer server.Close()
|
|
info, err := os.Lstat(path)
|
|
if err != nil || info.Mode().Perm() != 0600 {
|
|
t.Fatal("unprotected socket")
|
|
}
|
|
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
|
defer cancel()
|
|
session := &consoleIPCSession{}
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- server.Serve(ctx, func(context.Context, localreset.Peer) (localreset.Session, error) { return session, nil })
|
|
}()
|
|
t.Cleanup(func() {
|
|
cancel()
|
|
if err := <-done; err != nil {
|
|
t.Error(err)
|
|
}
|
|
})
|
|
conn, err := localreset.Dial(ctx, path, uint32(os.Geteuid()))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
screen := &recoveryTestScreen{}
|
|
switch action {
|
|
case "inspect":
|
|
screen.values = []string{"2", "1"}
|
|
case "reset":
|
|
screen.values = []string{"1", "1", "01234567", "NewPass123", "NewPass123"}
|
|
case "quit":
|
|
screen.values = []string{"q"}
|
|
}
|
|
if err := runConnected(ctx, conn, screen); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if action == "reset" {
|
|
if session.resets.Load() != 1 || session.selected.Load() != 1 || session.verified.Load() != 1 || session.inspected.Load() != 0 {
|
|
t.Fatal("reset verification flow changed")
|
|
}
|
|
} else {
|
|
if session.resets.Load() != 0 || session.selected.Load() != 0 || session.verified.Load() != 0 {
|
|
t.Fatal("non-reset menu invoked reset")
|
|
}
|
|
}
|
|
if action == "inspect" && (session.inspected.Load() != 1 || !strings.Contains(strings.Join(screen.lines, "|"), "只读采样已完成")) {
|
|
t.Fatal("inspection did not reach console")
|
|
}
|
|
})
|
|
}
|
|
}
|