211 lines
6.5 KiB
Go
211 lines
6.5 KiB
Go
package adminweb
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.zheromiku.top/Weknora/dobotai-platform/internal/dingtalkconfig"
|
|
"gitea.zheromiku.top/Weknora/dobotai-platform/internal/secretstore"
|
|
"gitea.zheromiku.top/Weknora/dobotai-platform/internal/settings"
|
|
)
|
|
|
|
type controlRepo struct {
|
|
savedVerified bool
|
|
savedRef string
|
|
candidateRef string
|
|
failedCode string
|
|
admins []settings.Administrator
|
|
disabledStaff string
|
|
disableErr error
|
|
}
|
|
|
|
func (*controlRepo) ActiveDingTalkConfig(context.Context, string) (string, string, bool, error) {
|
|
return "old", "old-ref", true, nil
|
|
}
|
|
func (r *controlRepo) CandidateDingTalkConfig(context.Context, string) (string, string, error) {
|
|
return "new", r.candidateRef, nil
|
|
}
|
|
func (r *controlRepo) SaveDingTalkConfig(_ context.Context, _, _, ref string, verified bool) error {
|
|
r.savedRef = ref
|
|
r.savedVerified = verified
|
|
r.candidateRef = ref
|
|
return nil
|
|
}
|
|
func (r *controlRepo) MarkDingTalkCandidateFailed(_ context.Context, _, _, code string) error {
|
|
r.failedCode = code
|
|
return nil
|
|
}
|
|
func (r *controlRepo) AdministratorCount(context.Context, string) (int, error) {
|
|
if r.admins == nil {
|
|
return 1, nil
|
|
}
|
|
n := 0
|
|
for _, admin := range r.admins {
|
|
if admin.Status == "active" {
|
|
n++
|
|
}
|
|
}
|
|
return n, nil
|
|
}
|
|
func (r *controlRepo) ListAdministrators(context.Context, string) ([]settings.Administrator, error) {
|
|
return r.admins, nil
|
|
}
|
|
func (r *controlRepo) DisableAdministrator(_ context.Context, _, staffID string) error {
|
|
r.disabledStaff = staffID
|
|
return r.disableErr
|
|
}
|
|
func (*controlRepo) ActiveBindings(context.Context, string) (map[string]settings.ResourceBinding, error) {
|
|
return map[string]settings.ResourceBinding{}, nil
|
|
}
|
|
func (*controlRepo) CreateBootstrapChallenge(context.Context, string, string, time.Time) error {
|
|
return nil
|
|
}
|
|
func (*controlRepo) ConfirmBootstrap(context.Context, string) error { return nil }
|
|
|
|
type verifyOK struct{}
|
|
|
|
func (verifyOK) Verify(context.Context, string, string) dingtalkconfig.Result {
|
|
return dingtalkconfig.Result{OK: true, Code: "oauth_ok"}
|
|
}
|
|
|
|
type activatorStub struct {
|
|
err error
|
|
calls int
|
|
}
|
|
|
|
func (a *activatorStub) ActivateCandidate(context.Context) error { a.calls++; return a.err }
|
|
|
|
type operationalStatusStub struct {
|
|
components []Component
|
|
err error
|
|
}
|
|
|
|
func (s operationalStatusStub) Components(context.Context) ([]Component, error) {
|
|
return s.components, s.err
|
|
}
|
|
|
|
func TestSnapshotIncludesOperationalComponents(t *testing.T) {
|
|
secrets, err := secretstore.New(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service, err := NewControlService("tenant", &controlRepo{}, secrets, verifyOK{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
checked := time.Unix(100, 0)
|
|
service.SetOperationalStatusProvider(operationalStatusStub{components: []Component{{
|
|
Name: "WeKnora", State: "ok", Code: "health_ok", CheckedAt: checked,
|
|
}}})
|
|
snapshot, err := service.Snapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, component := range snapshot.Components {
|
|
if component.Name == "WeKnora" && component.Code == "health_ok" && component.CheckedAt.Equal(checked) {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("components=%+v", snapshot.Components)
|
|
}
|
|
}
|
|
|
|
func TestSnapshotKeepsPageAvailableWhenOperationalProbeFails(t *testing.T) {
|
|
secrets, _ := secretstore.New(t.TempDir())
|
|
service, err := NewControlService("tenant", &controlRepo{}, secrets, verifyOK{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service.SetOperationalStatusProvider(operationalStatusStub{err: errors.New("probe failed")})
|
|
snapshot, err := service.Snapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
last := snapshot.Components[len(snapshot.Components)-1]
|
|
if last.Name != "运行探针" || last.State != "error" || last.Code != "component_probe_failed" {
|
|
t.Fatalf("component=%+v", last)
|
|
}
|
|
}
|
|
|
|
func TestSaveCredentialsCreatesCandidateNotActive(t *testing.T) {
|
|
secrets, err := secretstore.New(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo := &controlRepo{}
|
|
service, err := NewControlService("tenant", repo, secrets, verifyOK{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = service.SaveCredentials(context.Background(), "new", "secret"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if repo.savedVerified {
|
|
t.Fatal("candidate must not be active")
|
|
}
|
|
if repo.savedRef == "" || !secrets.Configured(repo.savedRef) {
|
|
t.Fatalf("ref=%q", repo.savedRef)
|
|
}
|
|
}
|
|
func TestActivateCandidateDelegatesAndMarksFailure(t *testing.T) {
|
|
secrets, err := secretstore.New(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo := &controlRepo{candidateRef: "candidate-ref"}
|
|
activator := &activatorStub{err: errors.New("candidate_start_failed_rolled_back")}
|
|
service, err := NewControlService("tenant", repo, secrets, verifyOK{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service.SetCandidateActivator(activator)
|
|
if err = service.ActivateCandidate(context.Background()); err == nil {
|
|
t.Fatal("expected failure")
|
|
}
|
|
if activator.calls != 1 || repo.failedCode != "activation_failed" {
|
|
t.Fatalf("calls=%d code=%q", activator.calls, repo.failedCode)
|
|
}
|
|
}
|
|
|
|
func TestSnapshotIncludesAdministratorsAndOnlyAllowsDisableWhenAnotherIsActive(t *testing.T) {
|
|
secrets, _ := secretstore.New(t.TempDir())
|
|
repo := &controlRepo{admins: []settings.Administrator{
|
|
{StaffID: "staff-old", DisplayName: "旧管理员", Department: "技术部", Role: "super_admin", Status: "active"},
|
|
{StaffID: "staff-new", DisplayName: "新管理员", Department: "产品部", Role: "super_admin", Status: "active"},
|
|
{StaffID: "staff-off", DisplayName: "停用管理员", Role: "admin", Status: "disabled"},
|
|
}}
|
|
service, err := NewControlService("tenant", repo, secrets, verifyOK{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
snapshot, err := service.Snapshot(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(snapshot.Administrators) != 3 || !snapshot.Administrators[0].CanDisable || !snapshot.Administrators[1].CanDisable || snapshot.Administrators[2].CanDisable {
|
|
t.Fatalf("administrators=%+v", snapshot.Administrators)
|
|
}
|
|
if snapshot.Administrators[0].StaffIDMasked == "staff-old" || snapshot.Administrators[0].StaffIDMasked == "" {
|
|
t.Fatalf("staff ID must be masked: %+v", snapshot.Administrators[0])
|
|
}
|
|
}
|
|
|
|
func TestControlServiceDelegatesAdministratorDisable(t *testing.T) {
|
|
secrets, _ := secretstore.New(t.TempDir())
|
|
repo := &controlRepo{}
|
|
service, err := NewControlService("tenant", repo, secrets, verifyOK{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = service.DisableAdministrator(context.Background(), "staff-old"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if repo.disabledStaff != "staff-old" {
|
|
t.Fatalf("disabled=%q", repo.disabledStaff)
|
|
}
|
|
}
|