Require confirmation and block deleting library actions still referenced by node groups.
This commit is contained in:
@@ -257,3 +257,147 @@ func TestActionsMutationsRequirePermissions(t *testing.T) {
|
||||
t.Fatalf("expected delete 403, got %d", deleteRec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsDeleteBlockedWhenLibraryReferenced(t *testing.T) {
|
||||
configDir := t.TempDir()
|
||||
cookie := seedCompletedSetup(t, configDir)
|
||||
node := seedNodeForActions(t, configDir)
|
||||
router := NewRouter(configDir)
|
||||
|
||||
createBody := []byte(`{"name":"Echo host","kind":"shell","body":"echo hi"}`)
|
||||
createReq := withSession(httptest.NewRequest(http.MethodPost, "/api/v1/actions", bytes.NewReader(createBody)), cookie)
|
||||
createRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(createRec, createReq)
|
||||
if createRec.Code != http.StatusOK {
|
||||
t.Fatalf("create status %d body=%s", createRec.Code, createRec.Body.String())
|
||||
}
|
||||
var created actionsResponseTest
|
||||
if err := json.NewDecoder(createRec.Body).Decode(&created); err != nil {
|
||||
t.Fatalf("decode create: %v", err)
|
||||
}
|
||||
customID := ""
|
||||
for _, action := range created.Actions {
|
||||
if !action.Builtin {
|
||||
customID = action.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
if customID == "" {
|
||||
t.Fatal("custom action missing")
|
||||
}
|
||||
|
||||
groupBody, _ := json.Marshal(map[string]any{
|
||||
"name": "Nightly",
|
||||
"schedule": map[string]any{
|
||||
"enabled": false,
|
||||
"kind": "interval",
|
||||
"every_seconds": 3600,
|
||||
"times": []any{},
|
||||
},
|
||||
})
|
||||
groupReq := withSession(
|
||||
httptest.NewRequest(http.MethodPost, "/api/v1/nodes/"+node.ID+"/action-groups", bytes.NewReader(groupBody)),
|
||||
cookie,
|
||||
)
|
||||
groupRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(groupRec, groupReq)
|
||||
if groupRec.Code != http.StatusOK {
|
||||
t.Fatalf("create group status=%d body=%s", groupRec.Code, groupRec.Body.String())
|
||||
}
|
||||
var groupResp actionGroupResponse
|
||||
if err := json.Unmarshal(groupRec.Body.Bytes(), &groupResp); err != nil {
|
||||
t.Fatalf("decode group: %v", err)
|
||||
}
|
||||
groupID := groupResp.Group.ID
|
||||
|
||||
addBody, _ := json.Marshal(map[string]any{
|
||||
"source": "library",
|
||||
"library_action_id": customID,
|
||||
})
|
||||
addReq := withSession(
|
||||
httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/api/v1/nodes/"+node.ID+"/action-groups/"+groupID+"/items",
|
||||
bytes.NewReader(addBody),
|
||||
),
|
||||
cookie,
|
||||
)
|
||||
addRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(addRec, addReq)
|
||||
if addRec.Code != http.StatusOK {
|
||||
t.Fatalf("add item status=%d body=%s", addRec.Code, addRec.Body.String())
|
||||
}
|
||||
var withItem actionGroupResponse
|
||||
if err := json.Unmarshal(addRec.Body.Bytes(), &withItem); err != nil {
|
||||
t.Fatalf("decode add: %v", err)
|
||||
}
|
||||
itemID := withItem.Group.Items[0].ID
|
||||
|
||||
blockedReq := withSession(
|
||||
httptest.NewRequest(http.MethodDelete, "/api/v1/actions?id="+customID, nil),
|
||||
cookie,
|
||||
)
|
||||
blockedRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(blockedRec, blockedReq)
|
||||
if blockedRec.Code != http.StatusConflict {
|
||||
t.Fatalf("expected delete conflict %d, got %d body=%s", http.StatusConflict, blockedRec.Code, blockedRec.Body.String())
|
||||
}
|
||||
var blockedErr apiErrorResponse
|
||||
if err := json.NewDecoder(blockedRec.Body).Decode(&blockedErr); err != nil {
|
||||
t.Fatalf("decode conflict: %v", err)
|
||||
}
|
||||
if blockedErr.Error != errActionInUse.Error() {
|
||||
t.Fatalf("conflict error = %q, want %q", blockedErr.Error, errActionInUse.Error())
|
||||
}
|
||||
|
||||
listReq := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/actions", nil), cookie)
|
||||
listRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(listRec, listReq)
|
||||
var listed actionsResponseTest
|
||||
if err := json.NewDecoder(listRec.Body).Decode(&listed); err != nil {
|
||||
t.Fatalf("decode list: %v", err)
|
||||
}
|
||||
stillPresent := false
|
||||
for _, action := range listed.Actions {
|
||||
if action.ID == customID {
|
||||
stillPresent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !stillPresent {
|
||||
t.Fatal("action should remain after blocked delete")
|
||||
}
|
||||
|
||||
cloneReq := withSession(
|
||||
httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/api/v1/nodes/"+node.ID+"/action-groups/"+groupID+"/items/"+itemID+"/clone",
|
||||
nil,
|
||||
),
|
||||
cookie,
|
||||
)
|
||||
cloneRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(cloneRec, cloneReq)
|
||||
if cloneRec.Code != http.StatusOK {
|
||||
t.Fatalf("clone status=%d body=%s", cloneRec.Code, cloneRec.Body.String())
|
||||
}
|
||||
|
||||
deleteReq := withSession(
|
||||
httptest.NewRequest(http.MethodDelete, "/api/v1/actions?id="+customID, nil),
|
||||
cookie,
|
||||
)
|
||||
deleteRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(deleteRec, deleteReq)
|
||||
if deleteRec.Code != http.StatusOK {
|
||||
t.Fatalf("delete after clone status %d body=%s", deleteRec.Code, deleteRec.Body.String())
|
||||
}
|
||||
var deleted actionsResponseTest
|
||||
if err := json.NewDecoder(deleteRec.Body).Decode(&deleted); err != nil {
|
||||
t.Fatalf("decode delete: %v", err)
|
||||
}
|
||||
for _, action := range deleted.Actions {
|
||||
if action.ID == customID {
|
||||
t.Fatal("custom action should be gone after delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user