feat(openapi): add unified /setting/* endpoints for OpenAPI
- Introduced handlers for the new /setting/* endpoints in the OpenAPI module. - Updated import statements to include the new setting package for better organization. - Enhanced routing capabilities to support the new settings functionality.
This commit is contained in:
parent
934424f9ea
commit
3bc15b1039
7 changed files with 505 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -83,3 +83,4 @@ agent/robot/ROBOT-CACHE-IMPROVEMENT.md
|
||||||
sandbox/v2/PID-KILL-UPGRADE.md
|
sandbox/v2/PID-KILL-UPGRADE.md
|
||||||
sandbox/v2/*.md
|
sandbox/v2/*.md
|
||||||
POSTGRESQL_COMPAT.md
|
POSTGRESQL_COMPAT.md
|
||||||
|
openapi/setting/*.md
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/yaoapp/yao/openapi/otp"
|
"github.com/yaoapp/yao/openapi/otp"
|
||||||
"github.com/yaoapp/yao/openapi/response"
|
"github.com/yaoapp/yao/openapi/response"
|
||||||
"github.com/yaoapp/yao/openapi/sandbox"
|
"github.com/yaoapp/yao/openapi/sandbox"
|
||||||
|
openAPISetting "github.com/yaoapp/yao/openapi/setting"
|
||||||
openapiTai "github.com/yaoapp/yao/openapi/tai"
|
openapiTai "github.com/yaoapp/yao/openapi/tai"
|
||||||
"github.com/yaoapp/yao/openapi/team"
|
"github.com/yaoapp/yao/openapi/team"
|
||||||
openapiTrace "github.com/yaoapp/yao/openapi/trace"
|
openapiTrace "github.com/yaoapp/yao/openapi/trace"
|
||||||
|
|
@ -199,6 +200,9 @@ func (openapi *OpenAPI) Attach(router *gin.Engine) {
|
||||||
group.POST("/tai-nodes/heartbeat", taiapi.HandleHeartbeat)
|
group.POST("/tai-nodes/heartbeat", taiapi.HandleHeartbeat)
|
||||||
group.DELETE("/tai-nodes/register/:tai_id", taiapi.HandleUnregister)
|
group.DELETE("/tai-nodes/register/:tai_id", taiapi.HandleUnregister)
|
||||||
|
|
||||||
|
// Setting handlers (unified /setting/* endpoints)
|
||||||
|
openAPISetting.Attach(group.Group("/setting"), openapi.OAuth)
|
||||||
|
|
||||||
// Custom handlers (Defined by developer)
|
// Custom handlers (Defined by developer)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
42
openapi/setting/promotions.yml
Normal file
42
openapi/setting/promotions.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Promotions & localized labels for the System Info page.
|
||||||
|
# Embedded at compile time via go:embed.
|
||||||
|
|
||||||
|
# --- Localized UI labels ---
|
||||||
|
labels:
|
||||||
|
deployment:
|
||||||
|
community:
|
||||||
|
zh: "社区版"
|
||||||
|
en: "Community"
|
||||||
|
starter:
|
||||||
|
zh: "入门版"
|
||||||
|
en: "Starter"
|
||||||
|
pro:
|
||||||
|
zh: "专业版"
|
||||||
|
en: "Pro"
|
||||||
|
enterprise:
|
||||||
|
zh: "企业版"
|
||||||
|
en: "Enterprise"
|
||||||
|
cloud:
|
||||||
|
zh: "Cloud"
|
||||||
|
en: "Cloud"
|
||||||
|
environment:
|
||||||
|
development:
|
||||||
|
zh: "测试环境"
|
||||||
|
en: "Development"
|
||||||
|
production:
|
||||||
|
zh: "正式环境"
|
||||||
|
en: "Production"
|
||||||
|
|
||||||
|
# --- Promotions by deployment type ---
|
||||||
|
community:
|
||||||
|
- id: upgrade-enterprise
|
||||||
|
link: "https://yaoagents.com/enterprise?source=yao-setting"
|
||||||
|
i18n:
|
||||||
|
zh:
|
||||||
|
title: "升级到企业版"
|
||||||
|
desc: "专属支持、私有部署、完全可控,行业 Agents 方案"
|
||||||
|
label: "了解更多 →"
|
||||||
|
en:
|
||||||
|
title: "Upgrade to Enterprise"
|
||||||
|
desc: "Dedicated support, private deployment, full control, industry-specific Agents solutions"
|
||||||
|
label: "Learn more →"
|
||||||
40
openapi/setting/setting.go
Normal file
40
openapi/setting/setting.go
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/yaoapp/yao/openapi/oauth/authorized"
|
||||||
|
oauthTypes "github.com/yaoapp/yao/openapi/oauth/types"
|
||||||
|
"github.com/yaoapp/yao/openapi/response"
|
||||||
|
"github.com/yaoapp/yao/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attach registers all /setting/* routes under the given group.
|
||||||
|
// Currently only System Info routes are wired; other groups will be
|
||||||
|
// added incrementally.
|
||||||
|
func Attach(group *gin.RouterGroup, oauth oauthTypes.OAuth) {
|
||||||
|
group.Use(oauth.Guard)
|
||||||
|
|
||||||
|
sys := group.Group("/system")
|
||||||
|
sys.GET("", handleSystemInfo)
|
||||||
|
sys.POST("/check-update", handleSystemCheckUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveOwner extracts the authenticated user/team from the Gin context
|
||||||
|
// and returns a setting.ScopeID suitable for registry operations.
|
||||||
|
func resolveOwner(c *gin.Context) setting.ScopeID {
|
||||||
|
info := authorized.GetInfo(c)
|
||||||
|
return setting.ScopeID{
|
||||||
|
Scope: setting.ScopeUser,
|
||||||
|
TeamID: info.TeamID,
|
||||||
|
UserID: info.UserID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// respondError is a thin helper that writes a JSON error via the shared
|
||||||
|
// response package.
|
||||||
|
func respondError(c *gin.Context, status int, msg string) {
|
||||||
|
response.RespondWithError(c, status, &response.ErrorResponse{
|
||||||
|
Code: "server_error",
|
||||||
|
ErrorDescription: msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
255
openapi/setting/system.go
Normal file
255
openapi/setting/system.go
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/yaoapp/yao/commercial"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/openapi/response"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const cdnBase = "https://get.yaoapps.com/yao"
|
||||||
|
|
||||||
|
// update check cache (package-level, protected by mutex)
|
||||||
|
var (
|
||||||
|
updateCache *CheckUpdateResult
|
||||||
|
updateCacheTime time.Time
|
||||||
|
updateMu sync.Mutex
|
||||||
|
cacheTTL = 10 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
// handleSystemInfo returns aggregated system information.
|
||||||
|
// GET /setting/system?locale=zh-cn
|
||||||
|
func handleSystemInfo(c *gin.Context) {
|
||||||
|
locale := strings.ToLower(c.DefaultQuery("locale", "en-us"))
|
||||||
|
|
||||||
|
env := share.App.Option["env"]
|
||||||
|
environment, _ := env.(string)
|
||||||
|
if environment == "" {
|
||||||
|
environment = config.Conf.Mode
|
||||||
|
}
|
||||||
|
if environment == "" {
|
||||||
|
environment = "production"
|
||||||
|
}
|
||||||
|
|
||||||
|
listen := fmt.Sprintf("%s:%d", config.Conf.Host, config.Conf.Port)
|
||||||
|
sessionStore := config.Conf.Session.Store
|
||||||
|
if sessionStore == "" {
|
||||||
|
sessionStore = "file"
|
||||||
|
}
|
||||||
|
|
||||||
|
lang := langFromLocale(locale)
|
||||||
|
lic := commercial.License
|
||||||
|
deployment := lic.Edition
|
||||||
|
if deployment == "" {
|
||||||
|
deployment = "community"
|
||||||
|
}
|
||||||
|
|
||||||
|
var licenseKey string
|
||||||
|
if lic.Valid && lic.SerialNumber != "" {
|
||||||
|
licenseKey = lic.SerialNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
data := SystemInfoData{
|
||||||
|
App: AppInfo{
|
||||||
|
Name: share.App.Name,
|
||||||
|
Short: share.App.Short,
|
||||||
|
Description: share.App.Description,
|
||||||
|
Logo: "/api/__yao/app/icons/app.png",
|
||||||
|
Version: share.App.Version,
|
||||||
|
},
|
||||||
|
Deployment: deployment,
|
||||||
|
DeploymentLabel: resolveLabel(promFile.Labels.Deployment, deployment, lang, deployment),
|
||||||
|
LicenseKey: licenseKey,
|
||||||
|
Environment: environment,
|
||||||
|
EnvironmentLabel: resolveLabel(promFile.Labels.Environment, environment, lang, environment),
|
||||||
|
Server: VersionInfo{
|
||||||
|
Version: share.VERSION,
|
||||||
|
BuildDate: share.PRVERSION,
|
||||||
|
CommitSHA: share.PRVERSION,
|
||||||
|
},
|
||||||
|
Client: VersionInfo{
|
||||||
|
Version: share.CUI,
|
||||||
|
BuildDate: share.PRCUI,
|
||||||
|
CommitSHA: share.PRCUI,
|
||||||
|
},
|
||||||
|
Technical: TechnicalInfo{
|
||||||
|
Listen: listen,
|
||||||
|
DBDriver: config.Conf.DB.Driver,
|
||||||
|
SessionStore: sessionStore,
|
||||||
|
},
|
||||||
|
Promotions: buildPromotions(deployment, locale),
|
||||||
|
}
|
||||||
|
|
||||||
|
response.RespondWithSuccess(c, http.StatusOK, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed promotions.yml
|
||||||
|
var promotionsYML []byte
|
||||||
|
|
||||||
|
type promotionEntry struct {
|
||||||
|
ID string `yaml:"id"`
|
||||||
|
Link string `yaml:"link"`
|
||||||
|
I18n map[string]promotionLocale `yaml:"i18n"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type promotionLocale struct {
|
||||||
|
Title string `yaml:"title"`
|
||||||
|
Desc string `yaml:"desc"`
|
||||||
|
Label string `yaml:"label"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type promotionsFile struct {
|
||||||
|
Labels struct {
|
||||||
|
Deployment map[string]map[string]string `yaml:"deployment"`
|
||||||
|
Environment map[string]map[string]string `yaml:"environment"`
|
||||||
|
} `yaml:"labels"`
|
||||||
|
Community []promotionEntry `yaml:"community"`
|
||||||
|
Enterprise []promotionEntry `yaml:"enterprise"`
|
||||||
|
Cloud []promotionEntry `yaml:"cloud"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var promFile promotionsFile
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
yaml.Unmarshal(promotionsYML, &promFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveLabel(m map[string]map[string]string, key, lang, fallback string) string {
|
||||||
|
if langs, ok := m[key]; ok {
|
||||||
|
if v, ok := langs[lang]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := langs["en"]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func langFromLocale(locale string) string {
|
||||||
|
if strings.HasPrefix(locale, "zh") {
|
||||||
|
return "zh"
|
||||||
|
}
|
||||||
|
return "en"
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPromotions(deployment, locale string) []Promotion {
|
||||||
|
lang := langFromLocale(locale)
|
||||||
|
|
||||||
|
var entries []promotionEntry
|
||||||
|
switch deployment {
|
||||||
|
case "community":
|
||||||
|
entries = promFile.Community
|
||||||
|
case "enterprise":
|
||||||
|
entries = promFile.Enterprise
|
||||||
|
case "cloud":
|
||||||
|
entries = promFile.Cloud
|
||||||
|
}
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
promos := make([]Promotion, 0, len(entries))
|
||||||
|
for _, e := range entries {
|
||||||
|
loc, ok := e.I18n[lang]
|
||||||
|
if !ok {
|
||||||
|
loc = e.I18n["en"]
|
||||||
|
}
|
||||||
|
promos = append(promos, Promotion{
|
||||||
|
ID: e.ID,
|
||||||
|
Title: loc.Title,
|
||||||
|
Desc: loc.Desc,
|
||||||
|
Link: e.Link,
|
||||||
|
Label: loc.Label,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return promos
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSystemCheckUpdate checks for a newer engine release.
|
||||||
|
// Uses the same CDN source as `yao upgrade` and yao-desktop:
|
||||||
|
//
|
||||||
|
// GET https://get.yaoapps.com/yao/latest.json
|
||||||
|
//
|
||||||
|
// POST /setting/system/check-update
|
||||||
|
func handleSystemCheckUpdate(c *gin.Context) {
|
||||||
|
updateMu.Lock()
|
||||||
|
if updateCache != nil && time.Since(updateCacheTime) < cacheTTL {
|
||||||
|
result := *updateCache
|
||||||
|
updateMu.Unlock()
|
||||||
|
response.RespondWithSuccess(c, http.StatusOK, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateMu.Unlock()
|
||||||
|
|
||||||
|
result := fetchLatestVersion()
|
||||||
|
|
||||||
|
updateMu.Lock()
|
||||||
|
updateCache = &result
|
||||||
|
updateCacheTime = time.Now()
|
||||||
|
updateMu.Unlock()
|
||||||
|
|
||||||
|
response.RespondWithSuccess(c, http.StatusOK, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cdnLatest mirrors the JSON structure of get.yaoapps.com/yao/latest.json
|
||||||
|
// (same format used by cmd/upgrade.go and yao-desktop updater.rs).
|
||||||
|
type cdnLatest struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
ReleasedAt string `json:"released_at"`
|
||||||
|
Assets map[string]string `json:"assets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchLatestVersion() CheckUpdateResult {
|
||||||
|
current := strings.TrimPrefix(share.VERSION, "v")
|
||||||
|
base := CheckUpdateResult{HasUpdate: false, CurrentVersion: current}
|
||||||
|
|
||||||
|
url := cdnBase + "/latest.json"
|
||||||
|
client := &http.Client{Timeout: 15 * time.Second}
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
req.Header.Set("User-Agent", fmt.Sprintf("yao/%s", share.VERSION))
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
var data cdnLatest
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
latest := strings.TrimPrefix(data.Version, "v")
|
||||||
|
if latest == "" {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
platformKey := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
downloadURL := data.Assets[platformKey]
|
||||||
|
|
||||||
|
return CheckUpdateResult{
|
||||||
|
HasUpdate: latest != current,
|
||||||
|
CurrentVersion: current,
|
||||||
|
LatestVersion: latest,
|
||||||
|
DownloadURL: downloadURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
55
openapi/setting/types.go
Normal file
55
openapi/setting/types.go
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package setting
|
||||||
|
|
||||||
|
// SystemInfoData is the top-level response for GET /setting/system.
|
||||||
|
type SystemInfoData struct {
|
||||||
|
App AppInfo `json:"app"`
|
||||||
|
Deployment string `json:"deployment"`
|
||||||
|
DeploymentLabel string `json:"deployment_label"`
|
||||||
|
LicenseKey string `json:"license_key,omitempty"`
|
||||||
|
Server VersionInfo `json:"server"`
|
||||||
|
Client VersionInfo `json:"client"`
|
||||||
|
Environment string `json:"environment"`
|
||||||
|
EnvironmentLabel string `json:"environment_label"`
|
||||||
|
Technical TechnicalInfo `json:"technical"`
|
||||||
|
Promotions []Promotion `json:"promotions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promotion is a localized CTA banner returned by the API.
|
||||||
|
type Promotion struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppInfo describes the running application.
|
||||||
|
type AppInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Short string `json:"short"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Logo string `json:"logo"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionInfo carries build metadata for a component (engine / CUI).
|
||||||
|
type VersionInfo struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
BuildDate string `json:"build_date"`
|
||||||
|
CommitSHA string `json:"commit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TechnicalInfo contains runtime / infrastructure details.
|
||||||
|
type TechnicalInfo struct {
|
||||||
|
Listen string `json:"listen"`
|
||||||
|
DBDriver string `json:"db_driver"`
|
||||||
|
SessionStore string `json:"session_store"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckUpdateResult is the response for POST /setting/system/check-update.
|
||||||
|
type CheckUpdateResult struct {
|
||||||
|
HasUpdate bool `json:"has_update"`
|
||||||
|
CurrentVersion string `json:"current_version"`
|
||||||
|
LatestVersion string `json:"latest_version,omitempty"`
|
||||||
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
|
}
|
||||||
108
openapi/tests/setting/system_test.go
Normal file
108
openapi/tests/setting/system_test.go
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
package setting_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/yao/openapi"
|
||||||
|
"github.com/yaoapp/yao/openapi/tests/testutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func baseURL() string {
|
||||||
|
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||||
|
return openapi.Server.Config.BaseURL
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSystemInfo verifies GET /setting/system returns the expected structure.
|
||||||
|
func TestSystemInfo(t *testing.T) {
|
||||||
|
serverURL := testutils.Prepare(t)
|
||||||
|
defer testutils.Clean()
|
||||||
|
|
||||||
|
client := testutils.RegisterTestClient(t, "Setting System Test", []string{"https://localhost/callback"})
|
||||||
|
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||||
|
token := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", serverURL+baseURL()+"/setting/system", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
var body map[string]interface{}
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Top-level keys
|
||||||
|
assert.Contains(t, body, "app")
|
||||||
|
assert.Contains(t, body, "deployment")
|
||||||
|
assert.Contains(t, body, "server")
|
||||||
|
assert.Contains(t, body, "client")
|
||||||
|
assert.Contains(t, body, "environment")
|
||||||
|
assert.Contains(t, body, "technical")
|
||||||
|
|
||||||
|
// app sub-fields
|
||||||
|
app, ok := body["app"].(map[string]interface{})
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NotEmpty(t, app["name"])
|
||||||
|
assert.NotEmpty(t, app["version"])
|
||||||
|
|
||||||
|
// server sub-fields
|
||||||
|
server, ok := body["server"].(map[string]interface{})
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NotEmpty(t, server["version"])
|
||||||
|
|
||||||
|
// technical sub-fields
|
||||||
|
tech, ok := body["technical"].(map[string]interface{})
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.NotEmpty(t, tech["listen"])
|
||||||
|
assert.NotEmpty(t, tech["db_driver"])
|
||||||
|
assert.NotEmpty(t, tech["session_store"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSystemInfoUnauthenticated verifies 401 when no token is provided.
|
||||||
|
func TestSystemInfoUnauthenticated(t *testing.T) {
|
||||||
|
serverURL := testutils.Prepare(t)
|
||||||
|
defer testutils.Clean()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", serverURL+baseURL()+"/setting/system", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSystemCheckUpdate verifies POST /setting/system/check-update returns has_update.
|
||||||
|
func TestSystemCheckUpdate(t *testing.T) {
|
||||||
|
serverURL := testutils.Prepare(t)
|
||||||
|
defer testutils.Clean()
|
||||||
|
|
||||||
|
client := testutils.RegisterTestClient(t, "Setting CheckUpdate Test", []string{"https://localhost/callback"})
|
||||||
|
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||||
|
token := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", serverURL+baseURL()+"/setting/system/check-update", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
var body map[string]interface{}
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, exists := body["has_update"]
|
||||||
|
assert.True(t, exists, "response must contain has_update field")
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue