186 lines
5.6 KiB
Go
186 lines
5.6 KiB
Go
package web
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"munin/internal/auth"
|
|
"munin/internal/store"
|
|
)
|
|
|
|
// Tests für den Download als ZIP (handlers_download.go).
|
|
|
|
// download ruft einen Download ab und gibt die Antwort zurück.
|
|
func download(h http.Handler, filePath string, cookie *http.Cookie) *httptest.ResponseRecorder {
|
|
r := httptest.NewRequest(http.MethodGet, filePath, nil)
|
|
r.AddCookie(cookie)
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, r)
|
|
return w
|
|
}
|
|
|
|
// zipContents öffnet ein heruntergeladenes ZIP und gibt Pfad → Inhalt zurück.
|
|
func zipContents(t *testing.T, data []byte) map[string]string {
|
|
t.Helper()
|
|
|
|
archive, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
|
if err != nil {
|
|
t.Fatalf("kein gültiges ZIP: %v", err)
|
|
}
|
|
content := make(map[string]string)
|
|
for _, f := range archive.File {
|
|
rc, err := f.Open()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, _ := io.ReadAll(rc)
|
|
rc.Close()
|
|
content[f.Name] = string(b)
|
|
}
|
|
return content
|
|
}
|
|
|
|
func TestProjectDownloadAsAdmin(t *testing.T) {
|
|
srv, db, project := testServer(t)
|
|
h := testHandler(t, srv)
|
|
ctx := context.Background()
|
|
|
|
if _, err := db.PutEntry(ctx, store.EntryWrite{
|
|
ProjectID: project.ID, Slug: "deployment", Title: "Deployment",
|
|
Content: "Wir deployen freitags nie.", Source: store.SourceHuman,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cookie := login(t, h, createKey(t, srv, db, project.ID, "verwalterin", auth.RoleAdmin))
|
|
w := download(h, "/p/testprojekt/export.zip", cookie)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("Status %d, erwartet 200: %s", w.Code, w.Body.String())
|
|
}
|
|
if ct := w.Header().Get("Content-Type"); ct != "application/zip" {
|
|
t.Errorf("Content-Type %q", ct)
|
|
}
|
|
if cd := w.Header().Get("Content-Disposition"); !strings.Contains(cd, `filename="munin-testprojekt-`) {
|
|
t.Errorf("Content-Disposition %q", cd)
|
|
}
|
|
|
|
content := zipContents(t, w.Body.Bytes())
|
|
if !strings.Contains(content["testprojekt/deployment.md"], "freitags nie") {
|
|
t.Errorf("Eintrag fehlt im ZIP, enthalten: %v", keysOf(content))
|
|
}
|
|
if _, ok := content["testprojekt/_index.md"]; !ok {
|
|
t.Error("_index.md fehlt im ZIP")
|
|
}
|
|
|
|
// Der Download steht im Protokoll des Projekts.
|
|
events, _ := db.ListAudit(ctx, store.AuditFilter{ProjectID: project.ID})
|
|
found := false
|
|
for _, e := range events {
|
|
if e.Action == store.ActionExportDownload && e.Actor == "verwalterin" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("Download fehlt im Protokoll")
|
|
}
|
|
}
|
|
|
|
// TestProjectDownloadOnlyForAdmins: Lesen und Schreiben reichen (vorerst)
|
|
// nicht, siehe Session.CanDownloadProject.
|
|
func TestProjectDownloadOnlyForAdmins(t *testing.T) {
|
|
srv, db, project := testServer(t)
|
|
h := testHandler(t, srv)
|
|
|
|
for _, role := range []auth.Role{auth.RoleRead, auth.RoleWrite} {
|
|
cookie := login(t, h, createKey(t, srv, db, project.ID, "key-"+string(role), role))
|
|
if w := download(h, "/p/testprojekt/export.zip", cookie); w.Code != http.StatusForbidden {
|
|
t.Errorf("Rolle %s: Status %d, erwartet 403", role, w.Code)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestProjectDownloadForeignProject: Der Admin von Projekt A kommt nicht an
|
|
// das ZIP von Projekt B.
|
|
func TestProjectDownloadForeignProject(t *testing.T) {
|
|
srv, db, project := testServer(t)
|
|
h := testHandler(t, srv)
|
|
|
|
if _, err := db.CreateProject(context.Background(), project.WorkspaceID, "anderes", "Anderes", ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cookie := login(t, h, createKey(t, srv, db, project.ID, "verwalterin", auth.RoleAdmin))
|
|
|
|
if w := download(h, "/p/anderes/export.zip", cookie); w.Code == http.StatusOK {
|
|
t.Error("ZIP eines fremden Projekts ausgeliefert")
|
|
}
|
|
}
|
|
|
|
func TestWorkspaceDownload(t *testing.T) {
|
|
srv, db, project := testServer(t)
|
|
h := testHandler(t, srv)
|
|
ctx := context.Background()
|
|
|
|
other, err := db.CreateProject(ctx, project.WorkspaceID, "anderes", "Anderes", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, p := range []store.Project{project, other} {
|
|
if _, err := db.PutEntry(ctx, store.EntryWrite{
|
|
ProjectID: p.ID, Slug: "notiz", Title: "Notiz", Content: "aus " + p.Slug, Source: store.SourceHuman,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
wsAdmin := loginAsWorkspaceAdmin(t, srv, h, project.WorkspaceID)
|
|
platform := loginAsPlatformAdmin(t, h, project.WorkspaceID)
|
|
|
|
for name, cookie := range map[string]*http.Cookie{"Workspace-Admin": wsAdmin.cookie, "Plattform-Admin": platform} {
|
|
w := download(h, "/admin/export.zip", cookie)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("%s: Status %d, erwartet 200", name, w.Code)
|
|
}
|
|
content := zipContents(t, w.Body.Bytes())
|
|
for _, filePath := range []string{"_workspace.md", "testprojekt/notiz.md", "anderes/notiz.md"} {
|
|
if _, ok := content[filePath]; !ok {
|
|
t.Errorf("%s: %s fehlt im ZIP, enthalten: %v", name, filePath, keysOf(content))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ein Projekt-Admin verwaltet keine Projekte, also auch kein Workspace-ZIP.
|
|
key := login(t, h, createKey(t, srv, db, project.ID, "verwalterin", auth.RoleAdmin))
|
|
if w := download(h, "/admin/export.zip", key); w.Code != http.StatusForbidden {
|
|
t.Errorf("Projekt-Admin: Status %d, erwartet 403", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestFilenamePart(t *testing.T) {
|
|
cases := map[string]string{
|
|
"Plattform": "plattform",
|
|
"Büro Süd & Nord": "buero-sued-nord",
|
|
`Böse "Name"` + "\r\n": "boese-name",
|
|
"!!!": "workspace",
|
|
"": "workspace",
|
|
}
|
|
for input, want := range cases {
|
|
if got := filenamePart(input, "workspace"); got != want {
|
|
t.Errorf("dateinamenTeil(%q) = %q, erwartet %q", input, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func keysOf(m map[string]string) []string {
|
|
var list []string
|
|
for k := range m {
|
|
list = append(list, k)
|
|
}
|
|
return list
|
|
}
|