Files
NianAIGC/backend/internal/httpapi/misc_test.go

162 lines
6.9 KiB
Go

package httpapi
import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/prompt"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/publicapi"
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/templates"
)
type miscResolver struct {
session identity.Session
err error
}
func (resolver miscResolver) Resolve(context.Context, string) (identity.Session, error) {
return resolver.session, resolver.err
}
type miscTemplateCatalog struct{ items []templates.Template }
func (c *miscTemplateCatalog) ListTemplates(context.Context, string) ([]templates.Template, error) {
return c.items, nil
}
func (c *miscTemplateCatalog) CreateTemplate(_ context.Context, item templates.Template) (templates.Template, error) {
c.items = append(c.items, item)
return item, nil
}
func (c *miscTemplateCatalog) UpdateTemplate(context.Context, string, string, templates.Patch, time.Time) (templates.Template, bool, error) {
return templates.Template{}, false, nil
}
func (c *miscTemplateCatalog) DeleteTemplate(context.Context, string, string) (templates.Template, bool, error) {
return templates.Template{}, false, nil
}
func TestMiscTemplateAndPromptRoutes(t *testing.T) {
version := 1
session := identity.Session{Version: 1, AuthMode: identity.AuthModeUser, SessionVersion: &version, User: identity.User{ID: "u1", ClientID: "platform", Role: "user"}}
authorizer, _ := NewPlatformAuthorizer(AuthState{Required: true, Configured: true}, miscResolver{session: session})
catalog := &miscTemplateCatalog{}
service := templates.NewService(catalog, func() time.Time { return time.Date(2026, 8, 13, 0, 0, 0, 0, time.UTC) }, func() string { return "t1" })
handler, err := NewMiscHandler(MiscDependencies{Platform: authorizer, Templates: service, PromptAssembler: prompt.Assemble})
if err != nil {
t.Fatal(err)
}
post := httptest.NewRequest(http.MethodPost, "/api/image-templates", bytes.NewBufferString(`{"name":" N ","prompt":" P "}`))
post.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "cookie"})
w := httptest.NewRecorder()
handler.ServeHTTP(w, post)
if w.Code != http.StatusCreated {
t.Fatalf("create status=%d body=%s", w.Code, w.Body.String())
}
get := httptest.NewRequest(http.MethodGet, "/api/image-templates", nil)
get.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "cookie"})
w = httptest.NewRecorder()
handler.ServeHTTP(w, get)
if w.Code != 200 || !bytes.Contains(w.Body.Bytes(), []byte(`"templates"`)) {
t.Fatalf("list status=%d body=%s", w.Code, w.Body.String())
}
assemble := httptest.NewRequest(http.MethodPost, "/api/prompt/assemble", bytes.NewBufferString(`{"mode":"image","manualPrompt":"@图片1","materials":[]}`))
assemble.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "cookie"})
w = httptest.NewRecorder()
handler.ServeHTTP(w, assemble)
if w.Code != 200 || !bytes.Contains(w.Body.Bytes(), []byte(`"requirements":{"image":1`)) {
t.Fatalf("prompt status=%d body=%s", w.Code, w.Body.String())
}
}
type settingsStub struct {
read any
saved map[string]any
err error
}
func (s *settingsStub) Get(context.Context) (any, error) { return s.read, s.err }
func (s *settingsStub) Save(_ context.Context, v map[string]any) (any, error) {
s.saved = v
return s.read, s.err
}
type logsStub struct {
entries any
cleared bool
err error
}
func (s *logsStub) List(context.Context, LogFilters) (any, error) { return s.entries, s.err }
func (s *logsStub) Clear(context.Context) error { s.cleared = true; return s.err }
func TestMiscSettingsLogsCapabilitiesAndOpenAPI(t *testing.T) {
session := identity.Session{AuthMode: identity.AuthModeAdmin, User: identity.User{ID: "root", ClientID: "platform", Role: "super_admin"}}
authorizer, _ := NewPlatformAuthorizer(AuthState{Required: true, Configured: true}, miscResolver{session: session})
settings := &settingsStub{read: map[string]any{"groups": []any{}}}
logs := &logsStub{entries: []any{map[string]any{"id": "l1"}}}
public := publicapi.NewAuthenticator(publicapi.Config{APIKeys: "client:key"})
handler, err := NewMiscHandler(MiscDependencies{Platform: authorizer, Settings: settings, Logs: logs, Public: public, Capabilities: func(context.Context) (any, error) { return []any{map[string]any{"id": "image.generate"}}, nil }})
if err != nil {
t.Fatal(err)
}
for _, path := range []string{"/api/settings", "/api/logs"} {
request := httptest.NewRequest(http.MethodGet, path, nil)
request.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "cookie"})
w := httptest.NewRecorder()
handler.ServeHTTP(w, request)
if w.Code != 200 {
t.Fatalf("%s status %d %s", path, w.Code, w.Body.String())
}
}
capRequest := httptest.NewRequest(http.MethodGet, "/api/v1/capabilities", nil)
capRequest.Header.Set("authorization", "Bearer key")
w := httptest.NewRecorder()
handler.ServeHTTP(w, capRequest)
if w.Code != 200 {
t.Fatalf("capabilities %d %s", w.Code, w.Body.String())
}
w = httptest.NewRecorder()
handler.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "https://app.example.test/api/v1/openapi.json", nil))
if w.Code != 200 || !bytes.Contains(w.Body.Bytes(), []byte(`"openapi":"3.1.0"`)) || !bytes.Contains(w.Body.Bytes(), []byte(`https://app.example.test`)) {
t.Fatalf("openapi %d %s", w.Code, w.Body.String())
}
}
func TestMiscOpenAPIUsesConfiguredPublicOrigin(t *testing.T) {
authorizer, _ := NewPlatformAuthorizer(AuthState{}, nil)
handler, err := NewMiscHandler(MiscDependencies{Platform: authorizer, PublicOrigin: "https://public.example.test/base/"})
if err != nil {
t.Fatal(err)
}
response := httptest.NewRecorder()
handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "http://internal:3000/api/v1/openapi.json", nil))
if response.Code != http.StatusOK || !bytes.Contains(response.Body.Bytes(), []byte(`"url":"https://public.example.test"`)) {
t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
}
}
func TestMiscFailsClosedAndHidesInfrastructureErrors(t *testing.T) {
authorizer, _ := NewPlatformAuthorizer(AuthState{Required: true, Configured: true}, miscResolver{err: errors.New("db password secret")})
handler, _ := NewMiscHandler(MiscDependencies{Platform: authorizer, PromptAssembler: prompt.Assemble})
request := httptest.NewRequest(http.MethodPost, "/api/prompt/assemble", bytes.NewBufferString(`{"mode":"image"}`))
request.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "cookie"})
w := httptest.NewRecorder()
handler.ServeHTTP(w, request)
if w.Code != 500 || bytes.Contains(w.Body.Bytes(), []byte("password")) {
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
}
w = httptest.NewRecorder()
handler.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/api/settings", nil))
var body map[string]any
_ = json.Unmarshal(w.Body.Bytes(), &body)
if w.Code != 401 {
t.Fatalf("status=%d body=%#v", w.Code, body)
}
}