package controllers import ( "context" "database/sql" "encoding/json" "net/http/httptest" "path/filepath" "testing" "time" "git.pcux.cn/xuans/Smail/db/migration" "git.pcux.cn/xuans/Smail/dto/response" "git.pcux.cn/xuans/Smail/services/access" "git.pcux.cn/xuans/Smail/services/logstore" appcontext "git.pcux.cn/xuans/Smail/utils/context" _ "modernc.org/sqlite" ) func TestLogsAPIRejectsUnscopedAccessAndValidatesQueries(t *testing.T) { database, err := sql.Open("sqlite", filepath.Join(t.TempDir(), "logs.db")) if err != nil { t.Fatal(err) } defer database.Close() // 与正式 SQLite 连接池一致,避免测试自行引入多写连接锁冲突。 database.SetMaxOpenConns(1) items := migration.DefaultMigrations() for _, statement := range items[len(items)-1].StatementsFor(migration.DialectSQLite) { if _, err := database.Exec(statement); err != nil { t.Fatal(err) } } store, err := logstore.New(database, migration.DialectSQLite) if err != nil { t.Fatal(err) } runtime := logstore.Start(store) defer runtime.Stop() if err := store.Write(context.Background(), logstore.Entry{Kind: "operation", CreatedAt: time.Now(), ActorID: "42", Action: "probe", Result: "success", Payload: []byte(`{}`)}); err != nil { t.Fatal(err) } call := func(role access.Role, query string) response.Response { t.Helper() request := httptest.NewRequest("GET", "/api/v1/logs?"+query, nil) recorder := httptest.NewRecorder() GetLogs(&appcontext.Context{Role: role, UserID: 42}, recorder, request) if recorder.Header().Get("Cache-Control") != "no-store" { t.Fatal("sensitive log response can be cached") } var result response.Response if err := json.Unmarshal(recorder.Body.Bytes(), &result); err != nil { t.Fatal(err) } return result } for _, role := range []access.Role{access.RoleUser, access.RoleAdmin} { for _, kind := range []string{"operation", "config", "mail", "runtime"} { if got := call(role, "kind="+kind+"&actorId=42"); got.ErrorNo != response.NoAccessPrivileges { t.Fatalf("unscoped role %s accessed %s: %d", role, kind, got.ErrorNo) } } } for _, query := range []string{"kind=unknown", "kind=mail&limit=501", "kind=mail&offset=-1", "kind=mail&from=invalid", "kind=mail&from=2026-09-08T00:00:00Z&to=2026-09-01T00:00:00Z"} { if got := call(access.RoleSuperAdmin, query); got.ErrorNo != response.ParamsError { t.Fatalf("invalid query accepted: %s => %d", query, got.ErrorNo) } } result := call(access.RoleSuperAdmin, "kind=operation&actorId=42&limit=1") if result.ErrorNo != 0 { t.Fatalf("administrator query failed: %d", result.ErrorNo) } data := result.Data.(map[string]any) if len(data["items"].([]any)) != 1 || data["retentionDays"] != float64(30) { t.Fatal("log query lost records or retention policy") } }