mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 18:15:47 +00:00
fix(skills): guard suggestion status updates by skill
This commit is contained in:
1 parent
7692939d09
commit
1e0ea7615d
3 files changed
+72
-8
No files matched your search
@@ -267,15 +267,20 @@ func (h *SkillsHandler) handleSuggestionStatus(w http.ResponseWriter, r *http.Re
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid suggestion id"})
|
||||
return
|
||||
}
|
||||
updated, err := h.evolutionStore.UpdateSuggestionStatus(r.Context(), suggestionID, status, "user", store.ActorIDFromContext(r.Context()))
|
||||
sg, err := h.evolutionStore.GetSuggestion(r.Context(), suggestionID)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if updated.SkillID != skillID {
|
||||
if sg == nil || sg.SkillID != skillID {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "suggestion not found"})
|
||||
return
|
||||
}
|
||||
updated, err := h.evolutionStore.UpdateSuggestionStatus(r.Context(), suggestionID, status, "user", store.ActorIDFromContext(r.Context()))
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
h.logSkillActivity(r, action, skillID, map[string]any{"suggestion_id": suggestionID.String()})
|
||||
writeJSON(w, http.StatusOK, updated)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package http
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -67,8 +68,45 @@ func TestApplySkillSuggestionPatchCreatesNewReferenceFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSuggestionStatusDoesNotUpdateDifferentSkillSuggestion(t *testing.T) {
|
||||
handler, skillStore, ctx, root := newTestUploadHandler(t)
|
||||
requestSkillID := skillStore.seedCustomSkill("request-skill", filepath.Join(root, "skills-store", "request-skill", "1"), "active", nil)
|
||||
ownerSkillID := skillStore.seedCustomSkill("owner-skill", filepath.Join(root, "skills-store", "owner-skill", "1"), "active", nil)
|
||||
suggestionID := uuid.New()
|
||||
evolution := &skillEvolutionStoreStub{
|
||||
suggestions: map[uuid.UUID]*store.SkillImprovementSuggestion{
|
||||
suggestionID: {
|
||||
ID: suggestionID,
|
||||
SkillID: ownerSkillID,
|
||||
Status: store.SkillSuggestionStatusPending,
|
||||
SuggestionType: "skill_reference_add",
|
||||
},
|
||||
},
|
||||
}
|
||||
handler.SetEvolutionStore(evolution, nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/skills/"+requestSkillID.String()+"/evolution/suggestions/"+suggestionID.String()+"/approve", nil).WithContext(ctx)
|
||||
req.SetPathValue("id", requestSkillID.String())
|
||||
req.SetPathValue("suggestionID", suggestionID.String())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.handleApproveSkillSuggestion(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("status = %d, body = %s, want 404", w.Code, w.Body.String())
|
||||
}
|
||||
if evolution.statusUpdates != 0 {
|
||||
t.Fatalf("status updates = %d, want 0", evolution.statusUpdates)
|
||||
}
|
||||
if got := evolution.suggestions[suggestionID].Status; got != store.SkillSuggestionStatusPending {
|
||||
t.Fatalf("suggestion status = %q, want pending", got)
|
||||
}
|
||||
}
|
||||
|
||||
type skillEvolutionStoreStub struct {
|
||||
versions []store.SkillVersion
|
||||
versions []store.SkillVersion
|
||||
suggestions map[uuid.UUID]*store.SkillImprovementSuggestion
|
||||
statusUpdates int
|
||||
}
|
||||
|
||||
func (s *skillEvolutionStoreStub) GetSettings(context.Context, uuid.UUID) (*store.SkillEvolutionSettings, error) {
|
||||
@@ -99,12 +137,33 @@ func (s *skillEvolutionStoreStub) ListSuggestions(context.Context, uuid.UUID, st
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *skillEvolutionStoreStub) GetSuggestion(context.Context, uuid.UUID) (*store.SkillImprovementSuggestion, error) {
|
||||
return nil, nil
|
||||
func (s *skillEvolutionStoreStub) GetSuggestion(_ context.Context, id uuid.UUID) (*store.SkillImprovementSuggestion, error) {
|
||||
if s.suggestions == nil {
|
||||
return nil, nil
|
||||
}
|
||||
sg, ok := s.suggestions[id]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
copied := *sg
|
||||
return &copied, nil
|
||||
}
|
||||
|
||||
func (s *skillEvolutionStoreStub) UpdateSuggestionStatus(context.Context, uuid.UUID, string, string, string) (*store.SkillImprovementSuggestion, error) {
|
||||
return nil, nil
|
||||
func (s *skillEvolutionStoreStub) UpdateSuggestionStatus(_ context.Context, id uuid.UUID, status, actorType, actorID string) (*store.SkillImprovementSuggestion, error) {
|
||||
s.statusUpdates++
|
||||
if s.suggestions == nil {
|
||||
return nil, fmt.Errorf("suggestion not found")
|
||||
}
|
||||
sg, ok := s.suggestions[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("suggestion not found")
|
||||
}
|
||||
sg.Status = status
|
||||
sg.ReviewedByActorType = actorType
|
||||
sg.ReviewedByActorID = actorID
|
||||
now := time.Now()
|
||||
sg.ReviewedAt = &now
|
||||
return sg, nil
|
||||
}
|
||||
|
||||
func (s *skillEvolutionStoreStub) MarkSuggestionApplied(_ context.Context, id uuid.UUID, version int, actorType, actorID string) (*store.SkillImprovementSuggestion, error) {
|
||||
|
||||
@@ -2104,7 +2104,7 @@ CREATE INDEX IF NOT EXISTS idx_browser_cookies_expires_at
|
||||
ON browser_cookies (expires_at);
|
||||
|
||||
-- ============================================================
|
||||
-- Skill self-evolution (migration 000078 / SQLite schema 47)
|
||||
-- Skill self-evolution (migration 000079 / SQLite schema 48)
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS skill_evolution_settings (
|
||||
|
||||
Reference in new issue
Block a user