Files
dobotai-platform/internal/settings/settings_test.go
2026-08-14 12:35:33 +08:00

134 lines
4.1 KiB
Go

package settings
import (
"context"
"errors"
"strings"
"testing"
)
func TestDetermineState(t *testing.T) {
cases := []struct {
name string
in Snapshot
want SystemState
}{
{"empty", Snapshot{}, StateUninitialized},
{"partial", Snapshot{ClientID: "id"}, StateCredentialError},
{"saved", Snapshot{ClientID: "id", SecretConfigured: true}, StateCredentialsSaved},
{"verified", Snapshot{ClientID: "id", SecretConfigured: true, CredentialsVerified: true}, StateWaitingAdmin},
{"active", Snapshot{ClientID: "id", SecretConfigured: true, CredentialsVerified: true, AdministratorCount: 1}, StateActive},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := DetermineState(tc.in); got != tc.want {
t.Fatalf("got %s want %s", got, tc.want)
}
})
}
}
func TestParseCommandStrict(t *testing.T) {
yes := []string{"/setting", "/设置", "/setting 123456", "/设置 123456", "/setting language ja", "/设置 language ja", "/setting support", "/设置 question https://alidocs.dingtalk.com/i/nodes/abc"}
for _, raw := range yes {
if c, ok := ParseCommand(raw); !ok || c.Name == "" {
t.Fatalf("must match %q", raw)
}
}
no := []string{"/help", "//help", "/settings", "//setting", "如何设置机器人", "abc /setting", "请解释/setting", "/settiing"}
for _, raw := range no {
if _, ok := ParseCommand(raw); ok {
t.Fatalf("must not match %q", raw)
}
}
}
func TestParseNotableLink(t *testing.T) {
got, err := ParseNotableLink("https://alidocs.dingtalk.com/i/nodes/base123?sheetId=sheet456&viewId=view789&recordId=row")
if err != nil {
t.Fatal(err)
}
if got.BaseID != "base123" || got.SheetID != "sheet456" || got.ViewID != "view789" {
t.Fatalf("got %+v", got)
}
if _, err = ParseNotableLink("http://evil.example/x?sheetId=s"); err == nil {
t.Fatal("expected rejection")
}
}
func TestParseNotableLinkAcceptsTrustedHostRelativeTail(t *testing.T) {
for _, raw := range []string{
"i/nodes/base123?sheetId=sheet456&viewId=view789&recordId=row",
"/i/nodes/base123?sheetId=sheet456&viewId=view789&recordId=row",
} {
got, err := ParseNotableLink(raw)
if err != nil {
t.Fatalf("ParseNotableLink(%q): %v", raw, err)
}
if got.BaseID != "base123" || got.SheetID != "sheet456" || got.ViewID != "view789" {
t.Fatalf("ParseNotableLink(%q)=%+v", raw, got)
}
}
}
func TestParseNotableLinkDecodesSheetAndViewFromIframeQuery(t *testing.T) {
got, err := ParseNotableLink("i/nodes/base123?iframeQuery=entrance%3Ddata%26sheetId%3Dsheet456%26viewId%3Dview789")
if err != nil {
t.Fatal(err)
}
if got.BaseID != "base123" || got.SheetID != "sheet456" || got.ViewID != "view789" {
t.Fatalf("got=%+v", got)
}
}
func TestParseNotableLinkRejectsConflictingIframeQueryIDs(t *testing.T) {
_, err := ParseNotableLink("i/nodes/base123?sheetId=sheet-top&iframeQuery=sheetId%3Dsheet-nested%26viewId%3Dview789")
if err == nil || !strings.Contains(err.Error(), "conflicting") {
t.Fatalf("err=%v", err)
}
}
func TestParseNotableLinkRejectsUnsafeOrAmbiguousRelativeTail(t *testing.T) {
for _, raw := range []string{
"//evil.example/i/nodes/base?sheetId=sheet",
"evil.example/i/nodes/base?sheetId=sheet",
"../i/nodes/base?sheetId=sheet",
"i/nodes/../base?sheetId=sheet",
"i/nodes/base",
"https://evil.example/i/nodes/base?sheetId=sheet",
} {
if _, err := ParseNotableLink(raw); err == nil {
t.Fatalf("ParseNotableLink(%q) must reject", raw)
}
}
}
type fakeRuntime struct {
active string
starts []string
fail string
}
func (f *fakeRuntime) Start(_ context.Context, id string) error {
f.starts = append(f.starts, id)
if id == f.fail {
return errors.New("boom")
}
f.active = id
return nil
}
func (f *fakeRuntime) Stop(_ context.Context) error { f.active = ""; return nil }
func TestSwitcherRollsBack(t *testing.T) {
f := &fakeRuntime{active: "old", fail: "new"}
s := NewSwitcher(f, "old")
if err := s.Switch(context.Background(), "new"); err == nil {
t.Fatal("expected error")
}
if f.active != "old" {
t.Fatalf("active=%q", f.active)
}
if len(f.starts) != 2 || f.starts[0] != "new" || f.starts[1] != "old" {
t.Fatalf("starts=%v", f.starts)
}
}