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) } }