mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-20 18:20:12 +00:00
test(agentcompat): add dashboard runtime harness
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
//go:build linux
|
||||
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/nezhahq/nezha/integration/agentcompat/internal/client"
|
||||
)
|
||||
|
||||
type patRequest struct {
|
||||
Name string `json:"name"`
|
||||
Scopes []string `json:"scopes"`
|
||||
ExpiresInDays int `json:"expires_in_days"`
|
||||
}
|
||||
|
||||
type patResponse struct {
|
||||
ID uint64 `json:"id"`
|
||||
Token string `json:"token"`
|
||||
Scopes []string `json:"scopes"`
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) bootstrapAuthentication(ctx context.Context) (patResponse, error) {
|
||||
readinessContext, cancel := context.WithTimeout(ctx, dashboard.readinessTimeout)
|
||||
defer cancel()
|
||||
retryTicker := time.NewTicker(dashboardRequestRetryPeriod)
|
||||
defer retryTicker.Stop()
|
||||
var login client.LoginResponse
|
||||
var err error
|
||||
for {
|
||||
login, err = dashboard.clients.REST.Login(readinessContext, client.LoginRequest{Username: "admin", Password: "admin"})
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-dashboard.supervisor.Exited():
|
||||
return patResponse{}, errors.New("dashboard process exited before login readiness")
|
||||
case <-readinessContext.Done():
|
||||
return patResponse{}, fmt.Errorf("dashboard login readiness: %w", errors.Join(err, readinessContext.Err()))
|
||||
case <-retryTicker.C:
|
||||
}
|
||||
}
|
||||
if login.Token == "" || login.Expire == "" {
|
||||
return patResponse{}, errors.New("dashboard login response omitted JWT metadata")
|
||||
}
|
||||
hasJWT, hasCSRF, err := dashboard.authenticationCookies()
|
||||
if err != nil {
|
||||
return patResponse{}, err
|
||||
}
|
||||
if !hasJWT || !hasCSRF {
|
||||
return patResponse{}, errors.New("dashboard login omitted authentication cookies")
|
||||
}
|
||||
pat, err := client.DoREST[patRequest, patResponse](readinessContext, dashboard.clients.REST, client.RESTRequest[patRequest]{
|
||||
Method: http.MethodPost,
|
||||
Path: "/api/v1/api-tokens",
|
||||
Body: &patRequest{
|
||||
Name: "agentcompat-admin",
|
||||
Scopes: []string{"nezha:*"},
|
||||
ExpiresInDays: 0,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return patResponse{}, fmt.Errorf("create dashboard PAT: %w", err)
|
||||
}
|
||||
if pat.ID == 0 || pat.Token == "" || len(pat.Scopes) != 1 || pat.Scopes[0] != "nezha:*" {
|
||||
return patResponse{}, errors.New("dashboard PAT response omitted wildcard administrator access")
|
||||
}
|
||||
dashboard.bootstrap = BootstrapResult{
|
||||
LoginAuthenticated: true,
|
||||
CSRFCookiePresent: true,
|
||||
PATID: pat.ID,
|
||||
PATScopes: append([]string(nil), pat.Scopes...),
|
||||
}
|
||||
return pat, nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) initializeAuthenticatedClients(ctx context.Context, pat patResponse) error {
|
||||
config := client.Config{BaseURL: dashboard.URL(), HTTPClient: dashboard.restHTTPClient, BearerToken: pat.Token}
|
||||
mcpClient, err := client.New(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
webSocketClient, err := client.New(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dashboard.clients.MCP = mcpClient
|
||||
dashboard.clients.WebSocket = webSocketClient
|
||||
initializeResult, err := mcpClient.Initialize(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initialize dashboard MCP: %w", err)
|
||||
}
|
||||
tools, err := mcpClient.ListTools(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list dashboard MCP tools: %w", err)
|
||||
}
|
||||
if initializeResult.ProtocolVersion != "2024-11-05" || initializeResult.ServerInfo.Name != "nezha-mcp" || len(tools.Tools) == 0 {
|
||||
return errors.New("dashboard MCP initialization returned incomplete capabilities")
|
||||
}
|
||||
dashboard.bootstrap.MCPProtocolVersion = initializeResult.ProtocolVersion
|
||||
dashboard.bootstrap.MCPServerName = initializeResult.ServerInfo.Name
|
||||
dashboard.bootstrap.MCPToolCount = len(tools.Tools)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dashboard *Dashboard) authenticationCookies() (bool, bool, error) {
|
||||
baseURL, err := url.Parse(dashboard.URL())
|
||||
if err != nil {
|
||||
return false, false, fmt.Errorf("parse dashboard URL: %w", err)
|
||||
}
|
||||
var hasJWT bool
|
||||
var hasCSRF bool
|
||||
for _, cookie := range dashboard.restHTTPClient.Jar.Cookies(baseURL) {
|
||||
switch cookie.Name {
|
||||
case "nz-jwt":
|
||||
hasJWT = cookie.Value != ""
|
||||
case "nz-csrf":
|
||||
hasCSRF = cookie.Value != ""
|
||||
}
|
||||
}
|
||||
return hasJWT, hasCSRF, nil
|
||||
}
|
||||
Reference in New Issue
Block a user