refac: article reads, story delete, no leaks, stored SEO status, rate limit, cleanup (%site%, article repo split, old lang repos, one default per site rule to domain)
This commit is contained in:
1 parent
9d284e65a9
commit
7c15206170
85 files changed
+1788
-593
No files matched your search
@@ -0,0 +1,98 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/api/pkg/apperr"
|
||||
"RenewCMS/internal/domain/audit"
|
||||
"net/http"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const storyDeletePath = "/v1/story/5"
|
||||
|
||||
func (w *storyWorld) storyVersionIDs() []uint32 {
|
||||
ids := []uint32{}
|
||||
for id, version := range w.articles.articles {
|
||||
if version.StoryID == storyStory {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
slices.Sort(ids)
|
||||
return ids
|
||||
}
|
||||
|
||||
func TestStoryDeleteHTTP(t *testing.T) {
|
||||
t.Run("Deleting an article removes every version and the article", func(t *testing.T) {
|
||||
w := newStoryWorld(t)
|
||||
|
||||
w.send(storyOwner, http.MethodDelete, storyDeletePath, "").expect(t, http.StatusNoContent, "")
|
||||
|
||||
if remaining := w.storyVersionIDs(); len(remaining) != 0 {
|
||||
t.Fatalf("versions left: %v", remaining)
|
||||
}
|
||||
w.send(storyOwner, http.MethodGet, "/v1/article/10", "").expect(t, http.StatusNotFound, apperr.ArticleNotFoundErrorMessage)
|
||||
deletes := 0
|
||||
for _, event := range w.recorder.events {
|
||||
if event.Action == audit.ActionArticleDelete && event.Params[audit.ParamStoryID] == storyStory {
|
||||
deletes++
|
||||
}
|
||||
}
|
||||
if deletes != 4 {
|
||||
t.Errorf("expected one article.delete per version, got %d in %v", deletes, w.recorder.actions())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Versions the caller cannot read are deleted with the article", func(t *testing.T) {
|
||||
w := newStoryWorld(t)
|
||||
|
||||
w.send(storyDeleter, http.MethodDelete, storyDeletePath, "").expect(t, http.StatusNoContent, "")
|
||||
|
||||
if remaining := w.storyVersionIDs(); len(remaining) != 0 {
|
||||
t.Fatalf("hidden versions left: %v", remaining)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Deleting an article needs delete permission on every version", func(t *testing.T) {
|
||||
w := newStoryWorld(t)
|
||||
before := w.storyVersionIDs()
|
||||
|
||||
response := w.send(storyWriter1, http.MethodDelete, storyDeletePath, "")
|
||||
|
||||
response.expect(t, http.StatusForbidden, apperr.MissingPermissionErrorMessage)
|
||||
if response.header(apperr.MissingPermissionHeader) != "content.delete" || !slices.Equal(w.storyVersionIDs(), before) {
|
||||
t.Fatalf("header %q, versions %v", response.header(apperr.MissingPermissionHeader), w.storyVersionIDs())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("A failed delete removes nothing", func(t *testing.T) {
|
||||
w := newStoryWorld(t)
|
||||
before := w.storyVersionIDs()
|
||||
w.articles.fail.failNext("DeleteStory")
|
||||
|
||||
w.send(storyOwner, http.MethodDelete, storyDeletePath, "").expect(t, http.StatusInternalServerError, apperr.DeleteArticleFailedErrorMessage)
|
||||
|
||||
if !slices.Equal(w.storyVersionIDs(), before) {
|
||||
t.Fatalf("versions changed: %v", w.storyVersionIDs())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("An unknown or unreadable article answers the same not found", func(t *testing.T) {
|
||||
w := newStoryWorld(t)
|
||||
w.put(w.stored(12))
|
||||
hidden := w.stored(11)
|
||||
hidden.StoryID = 77
|
||||
w.put(hidden)
|
||||
|
||||
w.send(storyOwner, http.MethodDelete, "/v1/story/9999", "").expect(t, http.StatusNotFound, apperr.ArticleNotFoundErrorMessage)
|
||||
w.send(storyDeleter, http.MethodDelete, "/v1/story/77", "").expect(t, http.StatusNotFound, apperr.ArticleNotFoundErrorMessage)
|
||||
if _, kept := w.articles.articles[11]; !kept {
|
||||
t.Fatal("an unreadable article must not be deleted")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("An invalid id is refused", func(t *testing.T) {
|
||||
w := newStoryWorld(t)
|
||||
|
||||
w.send(storyOwner, http.MethodDelete, "/v1/story/abc", "").expect(t, http.StatusBadRequest, apperr.IdUint32ErrorMessage)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user