Files
yetone a84f1052e0 groups: several routing groups are removed at once, and the found ones removed are one folded line rather than listed (lc on Discord: 路由组可否关闭自动发现,并且可编辑批量移除,已经移除的不要显示,或者用移除组来替代,这样干净整洁,并且可以彻删) — groups could only be removed one at a time from each editor, and every found group removed stayed under the list as a chip of its own. Select, by New group, puts a box on each group's card: a click on a card picks it, the bar's box picks all or none, and Remove (a second click to confirm, Remove 2? Click again) posts groups/delete {ids}, which provider.DeleteGroups removes all or none: refused before any goes when one isn't there or a group left holds one as a member or classifier, and a group held only by another being removed goes after it. A found group removed stays removed (magpie doesn't make it again), now one line under the list, 3 found groups removed, whose app menu brings one back; with found groups off it isn't shown. magpie group rm takes several ids. Turning found groups off altogether was there already (Find groups on their own, v0.1.603). zh, ja, de strings. TestDeleteGroups, TestGroupsDeleteSeveral; group-bulk-remove.test.cjs (WebKit, en and zh: picking, the two clicks, the post, the folded line and its menu, no click moves the page), failing on the old code (the chips). group-found-off, group-arrange, group-fold, settings-groups and click-scroll pass on WebKit. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-05 03:37:03 +08:00

144 lines
4.8 KiB
Go

package gui
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/yetone/magpie/internal/provider"
)
// The Routing view's save with another id than the group had (from)
// renames it: a found group drops its auto- prefix.
func TestGroupSaveRenames(t *testing.T) {
t.Setenv("HOME", t.TempDir())
t.Setenv("USERPROFILE", os.Getenv("HOME"))
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
t.Setenv("XDG_CACHE_HOME", t.TempDir())
for _, id := range []string{"a", "b"} {
if err := provider.Save(provider.Provider{ID: id, Name: id, Key: "k" + id, Chat: "http://127.0.0.1:1/v1", Models: []string{"gpt-6-astra"}}); err != nil {
t.Fatal(err)
}
}
mux := http.NewServeMux()
groupRoutes(mux)
post := func(body string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
mux.ServeHTTP(w, httptest.NewRequest("POST", "/api/groups/save", strings.NewReader(body)))
return w
}
const members = `"members":["a/gpt-6-astra","b/gpt-6-astra"]`
if w := post(`{"id":"Bad Id!","from":"auto-gpt-6-astra",` + members + `}`); w.Code < 400 {
t.Fatalf("a bad id taken: %d", w.Code)
}
if w := post(`{"id":"gpt-6-astra","from":"auto-gpt-6-astra","name":"Astra","routing":"order",` + members + `}`); w.Code != 200 {
t.Fatalf("%d %s", w.Code, w.Body)
}
g, _, ok := provider.FindGroup("group/gpt-6-astra")
if !ok || g.Name != "Astra" || g.Routing != provider.Ordered {
t.Fatalf("%v %+v", ok, g)
}
if _, _, ok := provider.FindGroup("group/auto-gpt-6-astra"); ok {
t.Fatal("auto-gpt-6-astra is back")
}
// a save without a new id stays a save
if w := post(`{"id":"gpt-6-astra","from":"gpt-6-astra","name":"Astra 2",` + members + `}`); w.Code != 200 {
t.Fatalf("%d %s", w.Code, w.Body)
}
if g, _, _ := provider.FindGroup("group/gpt-6-astra"); g.Name != "Astra 2" {
t.Fatalf("%+v", g)
}
}
// The Routing view's switch turns found groups off and on (蓝猫 on
// Discord): the state says which, and off lists none of them.
func TestGroupsFoundSwitch(t *testing.T) {
t.Setenv("HOME", t.TempDir())
t.Setenv("USERPROFILE", os.Getenv("HOME"))
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
t.Setenv("XDG_CACHE_HOME", t.TempDir())
for _, id := range []string{"a", "b"} {
if err := provider.Save(provider.Provider{ID: id, Name: id, Key: "k" + id, Chat: "http://127.0.0.1:1/v1", Models: []string{"m"}}); err != nil {
t.Fatal(err)
}
}
mux := http.NewServeMux()
groupRoutes(mux)
found := func(body string) groupsJSON {
t.Helper()
w := httptest.NewRecorder()
mux.ServeHTTP(w, httptest.NewRequest("POST", "/api/groups/found", strings.NewReader(body)))
if w.Code != 200 {
t.Fatalf("%s: %d %s", body, w.Code, w.Body)
}
var st groupsJSON
if err := json.Unmarshal(w.Body.Bytes(), &st); err != nil {
t.Fatal(err)
}
return st
}
if st := groupsState(); !st.Found || len(st.Groups) != 1 {
t.Fatalf("on: %v %+v", st.Found, st.Groups)
}
if st := found(`{"on":false}`); st.Found || len(st.Groups) != 0 || provider.AutoGroupsOn() {
t.Fatalf("off: %v %+v", st.Found, st.Groups)
}
if st := found(`{"on":true}`); !st.Found || len(st.Groups) != 1 || st.Groups[0].ID != "auto-m" {
t.Fatalf("on again: %v %+v", st.Found, st.Groups)
}
}
// groups/delete with ids removes them all at once (lc on Discord), or none
// when it can't remove one.
func TestGroupsDeleteSeveral(t *testing.T) {
t.Setenv("HOME", t.TempDir())
t.Setenv("USERPROFILE", os.Getenv("HOME"))
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
t.Setenv("XDG_CACHE_HOME", t.TempDir())
for _, id := range []string{"a", "b"} {
if err := provider.Save(provider.Provider{ID: id, Name: id, Key: "k" + id, Chat: "http://127.0.0.1:1/v1", Models: []string{"m", "n"}}); err != nil {
t.Fatal(err)
}
}
if err := provider.SaveGroup(provider.Group{Name: "Mine", Members: []string{"a/m", "b/n"}}); err != nil {
t.Fatal(err)
}
mux := http.NewServeMux()
groupRoutes(mux)
del := func(body string) (int, groupsJSON) {
w := httptest.NewRecorder()
mux.ServeHTTP(w, httptest.NewRequest("POST", "/api/groups/delete", strings.NewReader(body)))
var st groupsJSON
json.Unmarshal(w.Body.Bytes(), &st)
return w.Code, st
}
shown := func(st groupsJSON) (out []string) {
for _, g := range st.Groups {
if !g.Hidden {
out = append(out, g.ID)
}
}
return out
}
if code, _ := del(`{"ids":["auto-m","gone"]}`); code == 200 {
t.Fatal("a group not there was removed")
}
if got := shown(groupsState()); len(got) != 3 {
t.Fatalf("a refused removal removed some: %v", got)
}
code, st := del(`{"ids":["auto-m","mine"]}`)
if code != 200 {
t.Fatalf("%d", code)
}
if got := shown(st); len(got) != 1 || got[0] != "auto-n" {
t.Fatalf("left: %v", got)
}
// one by its id alone, as the editor's Remove does
if code, st := del(`{"id":"auto-n"}`); code != 200 || len(shown(st)) != 0 {
t.Fatalf("%d %v", code, shown(st))
}
}