mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-21 14:52:05 +02:00
feat: make the update source one repository address, the official repo by default
The update source used to be three things - a remote name or an address, plus an optional branch - and unset it followed whatever this directory's remotes and upstream happened to say. That asked people to understand git topology before they could update, and the person it was built for could not tell what "follow the remote this tree tracks" meant. It is now one field: config.yaml update.repo, an address, empty meaning the official repository (https://github.com/AIPentest/CyberStrikeAI.git). Point it at a mirror (GitHub is hard to reach in some places) or at your own fork and that is the whole configuration. The branch nothing is any more: an update follows the source repository's own default branch, resolved from the source itself (ls-remote --symref HEAD), so a fork whose default is not "main" still works and nobody has to name a branch. Remote names, the mine/origin/upstream ranking, @{upstream} tracking and the adopt flow's branch argument are all gone with it. The console page loses two input fields: the source section is one address, a hint that empty means the official repository, and a save button; clearing the address is the way back to the default. The install facts drop the remote name, the check section shows the source's default branch once the network answer arrives, and the adopt entry no longer requires saving an address first - the official default makes connecting a tarball install a zero-configuration action. The CLI now reads the same update.repo field the page saves (LoadUpdateRepo reads just that field, without validating the rest of the file - a broken unrelated section must not block the path that fixes the tree), so the keyboard and the button cannot update from different repositories. -check-update prints the source it is about to use. Tests: the source suite now pins the default (official), the single-address save (including the empty-string reset), and that the followed branch comes from the source's own default branch rather than a hardcoded "main"; the console suite posts the one field and renders the default hint; docs, config.example.yaml and upgrade.sh describe the one-field story.
This commit is contained in:
1 parent
e80668b2e9
commit
94a18263b9
28 files changed
+602
-584
No files matched your search
+12
-31
@@ -1912,24 +1912,20 @@ func (h *ConfigHandler) ApplyConfig(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// SetUpdateSource 记录一键更新的更新源(remote 与 remote_url 二选一,另有可选 branch)。
|
||||
// SetUpdateSource 记录一键更新的更新源(单个仓库地址;空 = 回到官方仓库)。
|
||||
// 走与其他设置相同的写入路径:备份旧文件、按 YAML 文档只改 update 段、原子落盘。
|
||||
func (h *ConfigHandler) SetUpdateSource(remote, remoteURL, branch string) error {
|
||||
func (h *ConfigHandler) SetUpdateSource(repo string) error {
|
||||
h.mu.Lock()
|
||||
h.config.Update = config.UpdateConfig{
|
||||
Remote: strings.TrimSpace(remote),
|
||||
RemoteURL: strings.TrimSpace(remoteURL),
|
||||
Branch: strings.TrimSpace(branch),
|
||||
}
|
||||
h.config.Update = config.UpdateConfig{Repo: strings.TrimSpace(repo)}
|
||||
h.mu.Unlock()
|
||||
return h.saveConfig()
|
||||
}
|
||||
|
||||
// UpdateSource 读当前生效的更新源(加读锁;更新处理器每次请求现读,保存后立即生效)。
|
||||
func (h *ConfigHandler) UpdateSource() (string, string, string) {
|
||||
// UpdateSource 读当前配置的更新源地址(加读锁;更新处理器每次请求现读,保存后立即生效)。
|
||||
func (h *ConfigHandler) UpdateSource() string {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.config.Update.Remote, h.config.Update.RemoteURL, h.config.Update.Branch
|
||||
return h.config.Update.Repo
|
||||
}
|
||||
|
||||
// saveConfig 保存配置到文件
|
||||
@@ -2086,33 +2082,18 @@ func updateMCPConfig(doc *yaml.Node, cfg config.MCPConfig) {
|
||||
setIntInMap(mcpNode, "port", cfg.Port)
|
||||
}
|
||||
|
||||
// updateUpdateSourceConfig 写入一键更新的更新源(update 段)。三项全空时把整段删掉,
|
||||
// 而不是留下一个空的 update: 块——"没有配置"应当就是文件里没有这一段。
|
||||
// updateUpdateSourceConfig 写入一键更新的更新源(update 段的单个 repo 字段)。
|
||||
// 留空时把整段删掉,而不是留下一个空的 update: 块——"没有配置"应当就是文件里没有这一段,
|
||||
// 运行时按官方仓库处理。
|
||||
func updateUpdateSourceConfig(doc *yaml.Node, cfg config.UpdateConfig) {
|
||||
root := doc.Content[0]
|
||||
remote := strings.TrimSpace(cfg.Remote)
|
||||
remoteURL := strings.TrimSpace(cfg.RemoteURL)
|
||||
branch := strings.TrimSpace(cfg.Branch)
|
||||
if remote == "" && remoteURL == "" && branch == "" {
|
||||
repo := strings.TrimSpace(cfg.Repo)
|
||||
if repo == "" {
|
||||
removeKeyFromMap(root, "update")
|
||||
return
|
||||
}
|
||||
node := ensureMap(root, "update")
|
||||
if remote != "" {
|
||||
setStringInMap(node, "remote", remote)
|
||||
} else {
|
||||
removeKeyFromMap(node, "remote")
|
||||
}
|
||||
if remoteURL != "" {
|
||||
setStringInMap(node, "remote_url", remoteURL)
|
||||
} else {
|
||||
removeKeyFromMap(node, "remote_url")
|
||||
}
|
||||
if branch != "" {
|
||||
setStringInMap(node, "branch", branch)
|
||||
} else {
|
||||
removeKeyFromMap(node, "branch")
|
||||
}
|
||||
setStringInMap(node, "repo", repo)
|
||||
}
|
||||
|
||||
func updateVisionConfig(doc *yaml.Node, cfg config.VisionConfig) {
|
||||
|
||||
@@ -3422,7 +3422,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
||||
"post": map[string]interface{}{
|
||||
"tags": []string{"系统更新"},
|
||||
"summary": "配置更新源(本安装从哪个仓库更新)",
|
||||
"description": "把更新源写进 config.yaml 的 update 段:remote(已有远端名)与 remote_url(仓库地址)二选一,branch 可选。不配置时更新跟随本目录自己跟踪的远端;地址白名单为 https/http/ssh/git/file:// 与本机绝对路径(git 的 ext:: 传输会执行命令,一律拒绝)。",
|
||||
"description": "把更新源写进 config.yaml 的 update 段:单个 repo 字段(仓库地址),更新跟随该仓库的默认分支。留空(或写空串)= 回到官方仓库 https://github.com/AIPentest/CyberStrikeAI.git。地址白名单为 https/http/ssh/git/file:// 与本机绝对路径(git 的 ext:: 传输会执行命令,一律拒绝)。",
|
||||
"operationId": "saveUpdateSource",
|
||||
"requestBody": map[string]interface{}{
|
||||
"content": map[string]interface{}{
|
||||
@@ -3430,17 +3430,15 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
||||
"schema": map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"remote": map[string]interface{}{"type": "string", "description": "安装树里已有的远端名(mine/origin/upstream/任意名)"},
|
||||
"remoteUrl": map[string]interface{}{"type": "string", "description": "仓库地址(与 remote 二选一)"},
|
||||
"branch": map[string]interface{}{"type": "string", "description": "要跟踪的分支;留空按 upstream/当前分支推断"},
|
||||
"repo": map[string]interface{}{"type": "string", "description": "更新源仓库地址;留空 = 官方仓库"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": map[string]interface{}{
|
||||
"200": map[string]interface{}{"description": "已保存(返回生效的更新源)"},
|
||||
"400": map[string]interface{}{"description": "远端名/地址/分支不合法,或两者同时给出"},
|
||||
"200": map[string]interface{}{"description": "已保存(返回配置值与官方默认地址)"},
|
||||
"400": map[string]interface{}{"description": "仓库地址不合法"},
|
||||
"401": map[string]interface{}{"description": "未授权"},
|
||||
},
|
||||
},
|
||||
@@ -3449,7 +3447,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
||||
"post": map[string]interface{}{
|
||||
"tags": []string{"系统更新"},
|
||||
"summary": "把非 git 目录接入更新源",
|
||||
"description": "解压/打包安装所在的目录接入配置好的更新源:不带 confirm 做无副作用预览(在临时仓库里 fetch,列出会被目标版本替换的文件与会保留的运维者内容);confirm=true 时异步执行(202 + job_id,用 /api/system/update/job 轮询):git init、添加 origin、落地目标分支内容,运维者内容先暂存再放回,被替换的文件全部留底到 .update-backup/<时间戳>/overwritten/。已是 git 工作树时返回 409(用一键更新而不是接入)。",
|
||||
"description": "解压/打包安装所在的目录接入配置好的更新源(未配置 = 官方仓库):不带 confirm 做无副作用预览(在临时仓库里 fetch,列出会被目标版本替换的文件与会保留的运维者内容);confirm=true 时异步执行(202 + job_id,用 /api/system/update/job 轮询):git init、添加 origin、落地更新源默认分支的内容,运维者内容先暂存再放回,被替换的文件全部留底到 .update-backup/<时间戳>/overwritten/。已是 git 工作树时返回 409(用一键更新而不是接入)。",
|
||||
"operationId": "adoptUpdateSource",
|
||||
"requestBody": map[string]interface{}{
|
||||
"content": map[string]interface{}{
|
||||
@@ -3467,7 +3465,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
||||
"responses": map[string]interface{}{
|
||||
"200": map[string]interface{}{"description": "预览结果(plan:将被替换/保留的文件清单与总数)"},
|
||||
"202": map[string]interface{}{"description": "接入任务已受理,返回 job_id"},
|
||||
"400": map[string]interface{}{"description": "没有配置更新源、地址不合法、分支不合法,或要求重启但没有重启钩子"},
|
||||
"400": map[string]interface{}{"description": "更新源地址不合法,或要求重启但没有重启钩子"},
|
||||
"409": map[string]interface{}{"description": "目录已是 git 工作树;或有任务在进行中"},
|
||||
"401": map[string]interface{}{"description": "未授权"},
|
||||
},
|
||||
@@ -6523,8 +6521,8 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
||||
"/api/system/update/check": map[string]interface{}{
|
||||
"post": map[string]interface{}{
|
||||
"tags": []string{"系统更新"},
|
||||
"summary": "检查本安装自己的远端有无新提交",
|
||||
"description": "对安装目录已经跟踪的远端分支执行 fetch,报告落后/领先提交数与新提交列表;取代码的来源永远是本目录自己的远端,不是任何写死的第三方仓库。检查失败返回 200 且带 checkError,不把「查不了」说成「已是最新」",
|
||||
"summary": "检查本安装的更新源有无新提交",
|
||||
"description": "对配置的更新源仓库(未配置 = 官方仓库 https://github.com/AIPentest/CyberStrikeAI.git)的默认分支执行 fetch,报告落后/领先提交数与新提交列表。检查失败返回 200 且带 checkError,不把「查不了」说成「已是最新」",
|
||||
"operationId": "checkUpdate",
|
||||
"responses": map[string]interface{}{
|
||||
"200": map[string]interface{}{
|
||||
@@ -6540,7 +6538,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
||||
"post": map[string]interface{}{
|
||||
"tags": []string{"系统更新"},
|
||||
"summary": "一键更新源码并重编译",
|
||||
"description": "快进到远端分支、重新编译二进制并原子换入(旧二进制留作 .prev 以便回滚)。本机改过源码文件或分支已分叉时拒绝执行并点名;roles/skills/tools/agents/bundles/knowledge_base/data/config.yaml 等运维者内容在合并前暂存、合并后放回,被保留的文件列在 keptContent。请求立即返回 job_id,进度用 GET /api/system/update/job 轮询;已有任务在跑时返回 409",
|
||||
"description": "快进到更新源仓库默认分支的最新提交、重新编译二进制并原子换入(旧二进制留作 .prev 以便回滚)。本机改过源码文件或分支已分叉时拒绝执行并点名;roles/skills/tools/agents/bundles/knowledge_base/data/config.yaml 等运维者内容在合并前暂存、合并后放回,被保留的文件列在 keptContent。请求立即返回 job_id,进度用 GET /api/system/update/job 轮询;已有任务在跑时返回 409",
|
||||
"operationId": "applyUpdate",
|
||||
"requestBody": map[string]interface{}{
|
||||
"content": map[string]interface{}{
|
||||
|
||||
+31
-42
@@ -36,13 +36,14 @@ type UpdateHandler struct {
|
||||
// request asked for a restart, because an unsupervised process that exits stays
|
||||
// exited - "I restarted you" would be a lie on most deployments.
|
||||
restart func()
|
||||
// source reports the configured update source (config.yaml update section) as the
|
||||
// operator last saved it; empty strings mean "not configured". A function rather
|
||||
// than a value because the config can change while the process runs.
|
||||
source func() (remote, remoteURL, branch string)
|
||||
// source reports the configured update source (config.yaml update.repo) as the
|
||||
// operator last saved it; an empty string means "not configured" and the official
|
||||
// repository is used. A function rather than a value because the config can change
|
||||
// while the process runs.
|
||||
source func() string
|
||||
// saveSource persists a new source through the config layer; nil means this start
|
||||
// has no config to write to.
|
||||
saveSource func(remote, remoteURL, branch string) error
|
||||
saveSource func(repo string) error
|
||||
// baseline is the binary this process is actually running: the file on disk at
|
||||
// construction time. An update, a rollback or a CLI run that replaces it leaves a
|
||||
// different file behind, and that difference is what "restart to activate" means.
|
||||
@@ -81,7 +82,7 @@ func (j *updateJob) view() *updateJob {
|
||||
// a SetAudit the assembly has to remember: forgetting here is a compile error instead of
|
||||
// a privileged endpoint that writes no audit records at all.
|
||||
func NewUpdateHandler(root string, logger *zap.Logger, auditSvc *audit.Service, restart func(),
|
||||
source func() (remote, remoteURL, branch string), saveSource func(remote, remoteURL, branch string) error) *UpdateHandler {
|
||||
source func() string, saveSource func(repo string) error) *UpdateHandler {
|
||||
return &UpdateHandler{
|
||||
root: root,
|
||||
logger: logger,
|
||||
@@ -121,20 +122,23 @@ func restartSupervised() bool {
|
||||
func (h *UpdateHandler) options() update.Options {
|
||||
opts := update.Options{Root: h.root}
|
||||
if h.source != nil {
|
||||
opts.Remote, opts.RemoteURL, opts.Branch = h.source()
|
||||
opts.Repo = h.source()
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// sourceView is the configured source as the page edits it (empty strings = unset).
|
||||
// sourceView is the configured source as the page edits it: the address (empty = the
|
||||
// official repository) plus the default, so the page never hardcodes a URL the server
|
||||
// would disagree with.
|
||||
func (h *UpdateHandler) sourceView() gin.H {
|
||||
var remote, remoteURL, branch string
|
||||
repo := ""
|
||||
if h.source != nil {
|
||||
remote, remoteURL, branch = h.source()
|
||||
repo = strings.TrimSpace(h.source())
|
||||
}
|
||||
return gin.H{
|
||||
"remote": remote, "remoteUrl": remoteURL, "branch": branch,
|
||||
"configured": remote != "" || remoteURL != "" || branch != "",
|
||||
"repo": repo,
|
||||
"configured": repo != "",
|
||||
"defaultRepo": update.DefaultRepoURL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,58 +171,43 @@ func (h *UpdateHandler) Check(c *gin.Context) {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
h.record(c, "update.check", resultOf(snap.CheckError), fmt.Sprintf("检查更新:%s/%s", snap.Remote, snap.Branch), map[string]interface{}{
|
||||
h.record(c, "update.check", resultOf(snap.CheckError), fmt.Sprintf("检查更新:%s(默认分支 %s)", snap.Repo, snap.TargetBranch), map[string]interface{}{
|
||||
"commit": snap.Commit, "remote_commit": snap.RemoteCommit, "behind": snap.Behind, "error": snap.CheckError,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{"status": snap})
|
||||
}
|
||||
|
||||
// SaveSource answers POST /api/system/update/source: the operator picks what this
|
||||
// installation updates from - the official repository, their own fork, or a second
|
||||
// development line - and the choice lands in the update section of config.yaml. A remote
|
||||
// name and an address are two ways to say the same thing and are refused together rather
|
||||
// than silently ordered.
|
||||
// SaveSource answers POST /api/system/update/source: the operator picks which repository
|
||||
// this installation updates from - the official one, a mirror, or their own fork - and
|
||||
// the choice lands in the update section of config.yaml. An empty address clears the
|
||||
// setting, which means the official repository again.
|
||||
func (h *UpdateHandler) SaveSource(c *gin.Context) {
|
||||
var body struct {
|
||||
Remote string `json:"remote"`
|
||||
RemoteURL string `json:"remoteUrl"`
|
||||
Branch string `json:"branch"`
|
||||
Repo string `json:"repo"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求体不是合法 JSON"})
|
||||
return
|
||||
}
|
||||
body.Remote = strings.TrimSpace(body.Remote)
|
||||
body.RemoteURL = strings.TrimSpace(body.RemoteURL)
|
||||
body.Branch = strings.TrimSpace(body.Branch)
|
||||
if body.Remote != "" && body.RemoteURL != "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "远端名与远端地址二选一:要么指名已有远端,要么直接给地址"})
|
||||
return
|
||||
}
|
||||
if body.Remote != "" && !update.ValidName(body.Remote) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "远端名不合法:" + body.Remote})
|
||||
return
|
||||
}
|
||||
if body.RemoteURL != "" && !update.ValidRemoteURL(body.RemoteURL) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "地址不合法(只允许 https/http/ssh/git/file:// 或本机绝对路径)"})
|
||||
return
|
||||
}
|
||||
if body.Branch != "" && !update.ValidName(body.Branch) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "分支名不合法:" + body.Branch})
|
||||
body.Repo = strings.TrimSpace(body.Repo)
|
||||
if body.Repo != "" && !update.ValidRemoteURL(body.Repo) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "仓库地址不合法(只允许 https/http/ssh/git/file:// 或本机绝对路径)"})
|
||||
return
|
||||
}
|
||||
if h.saveSource == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "本次启动没有接入配置保存,更新源改不了"})
|
||||
return
|
||||
}
|
||||
if err := h.saveSource(body.Remote, body.RemoteURL, body.Branch); err != nil {
|
||||
if err := h.saveSource(body.Repo); err != nil {
|
||||
h.record(c, "update.source", "failure", "保存更新源失败: "+err.Error(), map[string]interface{}{"error": err.Error()})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存失败:" + err.Error()})
|
||||
return
|
||||
}
|
||||
h.record(c, "update.source", "success", "更新源已保存", map[string]interface{}{
|
||||
"remote": body.Remote, "remote_url": body.RemoteURL, "branch": body.Branch,
|
||||
})
|
||||
message := "更新源已保存:" + body.Repo
|
||||
if body.Repo == "" {
|
||||
message = "已恢复默认更新源(官方仓库)"
|
||||
}
|
||||
h.record(c, "update.source", "success", message, map[string]interface{}{"repo": body.Repo})
|
||||
c.JSON(http.StatusOK, gin.H{"source": h.sourceView()})
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ func newUpdateRouter(root string, restart func()) (*gin.Engine, *UpdateHandler)
|
||||
}
|
||||
|
||||
func newUpdateRouterWithSource(root string, restart func(),
|
||||
source func() (string, string, string), save func(string, string, string) error) (*gin.Engine, *UpdateHandler) {
|
||||
source func() string, save func(string) error) (*gin.Engine, *UpdateHandler) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
h := NewUpdateHandler(root, nil, nil, restart, source, save)
|
||||
router := gin.New()
|
||||
@@ -191,17 +191,11 @@ func TestUpdateRollbackWithoutARecordedUpdateIsAConflict(t *testing.T) {
|
||||
func TestUpdateSourceSaveValidatesBeforeWriting(t *testing.T) {
|
||||
var saved []string
|
||||
router, _ := newUpdateRouterWithSource(t.TempDir(), nil,
|
||||
func() (string, string, string) { return "", "", "" },
|
||||
func(r, u, b string) error { saved = append(saved, r+"|"+u+"|"+b); return nil })
|
||||
func() string { return "" },
|
||||
func(repo string) error { saved = append(saved, repo); return nil })
|
||||
|
||||
// A name and an address are two ways to say the same thing; guessing which one the
|
||||
// operator meant is worse than asking again.
|
||||
w := doUpdate(router, http.MethodPost, "/api/system/update/source", `{"remote":"origin","remoteUrl":"https://github.com/x/y.git"}`)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d body = %s, want 400 for remote+remoteUrl together", w.Code, w.Body)
|
||||
}
|
||||
// git's ext:: transport runs commands; it must never reach the config.
|
||||
w = doUpdate(router, http.MethodPost, "/api/system/update/source", `{"remoteUrl":"ext::sh -c true"}`)
|
||||
w := doUpdate(router, http.MethodPost, "/api/system/update/source", `{"repo":"ext::sh -c true"}`)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d body = %s, want 400 for an ext:: address", w.Code, w.Body)
|
||||
}
|
||||
@@ -209,19 +203,28 @@ func TestUpdateSourceSaveValidatesBeforeWriting(t *testing.T) {
|
||||
t.Fatalf("a refused save must not reach the config layer: %v", saved)
|
||||
}
|
||||
|
||||
// A valid save is trimmed and lands in the config layer as three values.
|
||||
w = doUpdate(router, http.MethodPost, "/api/system/update/source", `{"remoteUrl":" https://github.com/Sycun/CyberStrikeAI.git ","branch":"main"}`)
|
||||
// A valid save is trimmed and lands in the config layer as one address.
|
||||
w = doUpdate(router, http.MethodPost, "/api/system/update/source", `{"repo":" https://github.com/Sycun/CyberStrikeAI.git "}`)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body = %s, want 200", w.Code, w.Body)
|
||||
}
|
||||
if len(saved) != 1 || saved[0] != "|https://github.com/Sycun/CyberStrikeAI.git|main" {
|
||||
t.Fatalf("saved = %v, want the trimmed address and branch", saved)
|
||||
if len(saved) != 1 || saved[0] != "https://github.com/Sycun/CyberStrikeAI.git" {
|
||||
t.Fatalf("saved = %v, want the trimmed address", saved)
|
||||
}
|
||||
|
||||
// An empty address is the way back to the official repository, not an error.
|
||||
w = doUpdate(router, http.MethodPost, "/api/system/update/source", `{"repo":""}`)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body = %s, want 200 for the reset-to-default save", w.Code, w.Body)
|
||||
}
|
||||
if len(saved) != 2 || saved[1] != "" {
|
||||
t.Fatalf("saved = %v, want the reset to reach the config layer", saved)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateStatusCarriesTheConfiguredSource(t *testing.T) {
|
||||
router, _ := newUpdateRouterWithSource(t.TempDir(), nil,
|
||||
func() (string, string, string) { return "", "https://github.com/Sycun/CyberStrikeAI.git", "main" }, nil)
|
||||
func() string { return "https://github.com/Sycun/CyberStrikeAI.git" }, nil)
|
||||
|
||||
w := doUpdate(router, http.MethodGet, "/api/system/update", "")
|
||||
if w.Code != http.StatusOK {
|
||||
@@ -229,22 +232,27 @@ func TestUpdateStatusCarriesTheConfiguredSource(t *testing.T) {
|
||||
}
|
||||
var body struct {
|
||||
Source struct {
|
||||
Remote string `json:"remote"`
|
||||
RemoteURL string `json:"remoteUrl"`
|
||||
Branch string `json:"branch"`
|
||||
Configured bool `json:"configured"`
|
||||
Repo string `json:"repo"`
|
||||
Configured bool `json:"configured"`
|
||||
DefaultRepo string `json:"defaultRepo"`
|
||||
} `json:"source"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !body.Source.Configured || body.Source.RemoteURL != "https://github.com/Sycun/CyberStrikeAI.git" || body.Source.Branch != "main" {
|
||||
t.Fatalf("source = %+v, want the configured address and branch", body.Source)
|
||||
if !body.Source.Configured || body.Source.Repo != "https://github.com/Sycun/CyberStrikeAI.git" {
|
||||
t.Fatalf("source = %+v, want the configured address", body.Source)
|
||||
}
|
||||
if body.Source.DefaultRepo != update.DefaultRepoURL {
|
||||
t.Fatalf("defaultRepo = %q, want the official repository the page offers as the default", body.Source.DefaultRepo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAdoptPreviewRefusesWithoutASource(t *testing.T) {
|
||||
router, _ := newUpdateRouter(t.TempDir(), nil)
|
||||
func TestUpdateAdoptRefusesAnAddressGitMustNeverSee(t *testing.T) {
|
||||
// A plain directory with an unusable source: the refusal must come back before any
|
||||
// fetch, so this test never touches the network.
|
||||
router, _ := newUpdateRouterWithSource(t.TempDir(), nil,
|
||||
func() string { return "ext::sh -c true" }, nil)
|
||||
|
||||
w := doUpdate(router, http.MethodPost, "/api/system/update/adopt", `{}`)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
@@ -254,8 +262,8 @@ func TestUpdateAdoptPreviewRefusesWithoutASource(t *testing.T) {
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body["reason"] != "no_source" {
|
||||
t.Fatalf("reason = %v, want no_source so the page can say what is missing", body["reason"])
|
||||
if body["reason"] != "bad_source" {
|
||||
t.Fatalf("reason = %v, want bad_source so the page can say what is wrong", body["reason"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user