Files
sub2api-cn-relay-manager/internal/store/sqlite/logical_groups_repo_test.go
2026-05-28 15:57:34 +08:00

309 lines
9.7 KiB
Go

package sqlite
import (
"context"
"database/sql"
"errors"
"testing"
)
func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
store := openTestDB(t)
ctx := context.Background()
id, err := store.LogicalGroups().Create(ctx, LogicalGroup{
LogicalGroupID: "gpt-shared",
DisplayName: "GPT Shared",
Status: "active",
Description: "shared group",
})
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if id <= 0 {
t.Fatalf("Create() id = %d, want positive", id)
}
group, err := store.LogicalGroups().GetByLogicalGroupID(ctx, "gpt-shared")
if err != nil {
t.Fatalf("GetByLogicalGroupID() error = %v", err)
}
if group.RoutePolicy != defaultLogicalGroupRoutePolicy {
t.Fatalf("RoutePolicy = %q, want %q", group.RoutePolicy, defaultLogicalGroupRoutePolicy)
}
if group.StickyMode != defaultLogicalGroupStickyMode {
t.Fatalf("StickyMode = %q, want %q", group.StickyMode, defaultLogicalGroupStickyMode)
}
if err := store.LogicalGroups().UpdateByLogicalGroupID(ctx, LogicalGroup{
LogicalGroupID: "gpt-shared",
DisplayName: "GPT Shared Updated",
Status: "paused",
Description: "updated",
RoutePolicy: "priority",
StickyMode: "user_preferred",
ConversationTTLSeconds: 3600,
UserModelTTLSeconds: 900,
FailoverThreshold: 3,
CooldownSeconds: 120,
}); err != nil {
t.Fatalf("UpdateByLogicalGroupID() error = %v", err)
}
updated, err := store.LogicalGroups().GetByLogicalGroupID(ctx, "gpt-shared")
if err != nil {
t.Fatalf("GetByLogicalGroupID(updated) error = %v", err)
}
if updated.DisplayName != "GPT Shared Updated" || updated.Status != "paused" {
t.Fatalf("updated group = %+v, want updated fields", updated)
}
if err := store.LogicalGroups().DeleteByLogicalGroupID(ctx, "gpt-shared"); err != nil {
t.Fatalf("DeleteByLogicalGroupID() error = %v", err)
}
_, err = store.LogicalGroups().GetByLogicalGroupID(ctx, "gpt-shared")
if !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("GetByLogicalGroupID() after delete error = %v, want sql.ErrNoRows", err)
}
}
func TestLogicalGroupsRepoList(t *testing.T) {
store := openTestDB(t)
ctx := context.Background()
for _, group := range []LogicalGroup{
{LogicalGroupID: "group-a", DisplayName: "Group A", Status: "active"},
{LogicalGroupID: "group-b", DisplayName: "Group B", Status: "active"},
} {
if _, err := store.LogicalGroups().Create(ctx, group); err != nil {
t.Fatalf("Create(%q) error = %v", group.LogicalGroupID, err)
}
}
groups, err := store.LogicalGroups().List(ctx)
if err != nil {
t.Fatalf("List() error = %v", err)
}
if len(groups) != 2 {
t.Fatalf("List() len = %d, want 2", len(groups))
}
if groups[0].LogicalGroupID != "group-a" || groups[1].LogicalGroupID != "group-b" {
t.Fatalf("List() = %+v, want insertion order", groups)
}
}
func TestLogicalGroupModelsRepoCreateListDelete(t *testing.T) {
store := openTestDBWithFK(t)
ctx := context.Background()
if _, err := store.LogicalGroups().Create(ctx, LogicalGroup{
LogicalGroupID: "gpt-shared",
DisplayName: "GPT Shared",
Status: "active",
}); err != nil {
t.Fatalf("LogicalGroups().Create() error = %v", err)
}
if _, err := store.LogicalGroupModels().Create(ctx, LogicalGroupModel{
LogicalGroupID: "gpt-shared",
PublicModel: "gpt-5.4",
}); err != nil {
t.Fatalf("LogicalGroupModels().Create() error = %v", err)
}
models, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, "gpt-shared")
if err != nil {
t.Fatalf("ListByLogicalGroupID() error = %v", err)
}
if len(models) != 1 || models[0].PublicModel != "gpt-5.4" {
t.Fatalf("ListByLogicalGroupID() = %+v, want gpt-5.4", models)
}
if err := store.LogicalGroupModels().DeleteByLogicalGroupIDAndModel(ctx, "gpt-shared", "gpt-5.4"); err != nil {
t.Fatalf("DeleteByLogicalGroupIDAndModel() error = %v", err)
}
models, err = store.LogicalGroupModels().ListByLogicalGroupID(ctx, "gpt-shared")
if err != nil {
t.Fatalf("ListByLogicalGroupID() after delete error = %v", err)
}
if len(models) != 0 {
t.Fatalf("ListByLogicalGroupID() after delete len = %d, want 0", len(models))
}
}
func TestLogicalGroupRoutesRepoCreateGetListUpdateDelete(t *testing.T) {
store := openTestDBWithFK(t)
ctx := context.Background()
if _, err := store.LogicalGroups().Create(ctx, LogicalGroup{
LogicalGroupID: "gpt-shared",
DisplayName: "GPT Shared",
Status: "active",
}); err != nil {
t.Fatalf("LogicalGroups().Create() error = %v", err)
}
if _, err := store.LogicalGroupRoutes().Create(ctx, LogicalGroupRoute{
RouteID: "asxs",
LogicalGroupID: "gpt-shared",
Name: "ASXS",
Status: "active",
Priority: 10,
ShadowGroupID: "gpt-shared__asxs",
ShadowHostID: "remote43",
}); err != nil {
t.Fatalf("LogicalGroupRoutes().Create() error = %v", err)
}
route, err := store.LogicalGroupRoutes().GetByRouteID(ctx, "asxs")
if err != nil {
t.Fatalf("GetByRouteID() error = %v", err)
}
if route.Weight != 100 {
t.Fatalf("Weight = %d, want 100", route.Weight)
}
routes, err := store.LogicalGroupRoutes().ListByLogicalGroupID(ctx, "gpt-shared")
if err != nil {
t.Fatalf("ListByLogicalGroupID() error = %v", err)
}
if len(routes) != 1 || routes[0].RouteID != "asxs" {
t.Fatalf("ListByLogicalGroupID() = %+v, want route asxs", routes)
}
if err := store.LogicalGroupRoutes().UpdateByRouteID(ctx, LogicalGroupRoute{
RouteID: "asxs",
LogicalGroupID: "gpt-shared",
Name: "ASXS Updated",
Status: "degraded",
Priority: 20,
Weight: 80,
ShadowGroupID: "gpt-shared__asxs",
ShadowHostID: "remote43",
UpstreamBaseURLHint: "https://api.asxs.top/v1",
CooldownUntil: "2026-05-28T16:00:00Z",
}); err != nil {
t.Fatalf("UpdateByRouteID() error = %v", err)
}
updated, err := store.LogicalGroupRoutes().GetByRouteID(ctx, "asxs")
if err != nil {
t.Fatalf("GetByRouteID(updated) error = %v", err)
}
if updated.Name != "ASXS Updated" || updated.Status != "degraded" || updated.Weight != 80 {
t.Fatalf("updated route = %+v, want updated fields", updated)
}
if err := store.LogicalGroupRoutes().DeleteByRouteID(ctx, "asxs"); err != nil {
t.Fatalf("DeleteByRouteID() error = %v", err)
}
_, err = store.LogicalGroupRoutes().GetByRouteID(ctx, "asxs")
if !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("GetByRouteID() after delete error = %v, want sql.ErrNoRows", err)
}
}
func TestLogicalGroupRouteModelsRepoCreateList(t *testing.T) {
store := openTestDBWithFK(t)
ctx := context.Background()
if _, err := store.LogicalGroups().Create(ctx, LogicalGroup{
LogicalGroupID: "gpt-shared",
DisplayName: "GPT Shared",
Status: "active",
}); err != nil {
t.Fatalf("LogicalGroups().Create() error = %v", err)
}
if _, err := store.LogicalGroupRoutes().Create(ctx, LogicalGroupRoute{
RouteID: "codex2api",
LogicalGroupID: "gpt-shared",
Name: "Codex2API",
Status: "active",
Priority: 20,
ShadowGroupID: "gpt-shared__codex2api",
ShadowHostID: "remote43",
}); err != nil {
t.Fatalf("LogicalGroupRoutes().Create() error = %v", err)
}
if _, err := store.LogicalGroupRouteModels().Create(ctx, LogicalGroupRouteModel{
RouteID: "codex2api",
PublicModel: "gpt-5.4",
}); err != nil {
t.Fatalf("LogicalGroupRouteModels().Create() error = %v", err)
}
models, err := store.LogicalGroupRouteModels().ListByRouteID(ctx, "codex2api")
if err != nil {
t.Fatalf("ListByRouteID() error = %v", err)
}
if len(models) != 1 {
t.Fatalf("ListByRouteID() len = %d, want 1", len(models))
}
if models[0].ShadowModel != "gpt-5.4" {
t.Fatalf("ShadowModel = %q, want default public model", models[0].ShadowModel)
}
}
func TestLogicalGroupReposEnforceForeignKeysAndCascadeDelete(t *testing.T) {
store := openTestDBWithFK(t)
ctx := context.Background()
if _, err := store.LogicalGroupModels().Create(ctx, LogicalGroupModel{
LogicalGroupID: "missing-group",
PublicModel: "gpt-5.4",
}); err == nil {
t.Fatal("LogicalGroupModels().Create() error = nil, want foreign key failure")
}
if _, err := store.LogicalGroups().Create(ctx, LogicalGroup{
LogicalGroupID: "gpt-shared",
DisplayName: "GPT Shared",
Status: "active",
}); err != nil {
t.Fatalf("LogicalGroups().Create() error = %v", err)
}
if _, err := store.LogicalGroupModels().Create(ctx, LogicalGroupModel{
LogicalGroupID: "gpt-shared",
PublicModel: "gpt-5.4",
}); err != nil {
t.Fatalf("LogicalGroupModels().Create() error = %v", err)
}
if _, err := store.LogicalGroupRoutes().Create(ctx, LogicalGroupRoute{
RouteID: "asxs",
LogicalGroupID: "gpt-shared",
Name: "ASXS",
Status: "active",
Priority: 10,
ShadowGroupID: "gpt-shared__asxs",
ShadowHostID: "remote43",
}); err != nil {
t.Fatalf("LogicalGroupRoutes().Create() error = %v", err)
}
if _, err := store.LogicalGroupRouteModels().Create(ctx, LogicalGroupRouteModel{
RouteID: "asxs",
PublicModel: "gpt-5.4",
}); err != nil {
t.Fatalf("LogicalGroupRouteModels().Create() error = %v", err)
}
if err := store.LogicalGroups().DeleteByLogicalGroupID(ctx, "gpt-shared"); err != nil {
t.Fatalf("DeleteByLogicalGroupID() error = %v", err)
}
models, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, "gpt-shared")
if err != nil {
t.Fatalf("ListByLogicalGroupID() after cascade error = %v", err)
}
if len(models) != 0 {
t.Fatalf("models after cascade len = %d, want 0", len(models))
}
routes, err := store.LogicalGroupRoutes().ListByLogicalGroupID(ctx, "gpt-shared")
if err != nil {
t.Fatalf("ListByLogicalGroupID(routes) after cascade error = %v", err)
}
if len(routes) != 0 {
t.Fatalf("routes after cascade len = %d, want 0", len(routes))
}
}