munin/internal/web/download_test.go

186 lines
5.7 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).
// ladeHerunter ruft einen Download ab und gibt die Antwort zurück.
func ladeHerunter(h http.Handler, pfad string, cookie *http.Cookie) *httptest.ResponseRecorder {
r := httptest.NewRequest(http.MethodGet, pfad, nil)
r.AddCookie(cookie)
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
return w
}
// zipInhalt öffnet ein heruntergeladenes ZIP und gibt Pfad → Inhalt zurück.
func zipInhalt(t *testing.T, daten []byte) map[string]string {
t.Helper()
archiv, err := zip.NewReader(bytes.NewReader(daten), int64(len(daten)))
if err != nil {
t.Fatalf("kein gültiges ZIP: %v", err)
}
inhalt := make(map[string]string)
for _, f := range archiv.File {
rc, err := f.Open()
if err != nil {
t.Fatal(err)
}
b, _ := io.ReadAll(rc)
rc.Close()
inhalt[f.Name] = string(b)
}
return inhalt
}
func TestProjektDownloadAlsAdmin(t *testing.T) {
srv, db, projekt := testServer(t)
h := testHandler(t, srv)
ctx := context.Background()
if _, err := db.PutEntry(ctx, store.EntryWrite{
ProjectID: projekt.ID, Slug: "deployment", Title: "Deployment",
Content: "Wir deployen freitags nie.", Source: store.SourceHuman,
}); err != nil {
t.Fatal(err)
}
cookie := anmelden(t, h, erzeugeKey(t, srv, db, projekt.ID, "verwalterin", auth.RoleAdmin))
w := ladeHerunter(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)
}
inhalt := zipInhalt(t, w.Body.Bytes())
if !strings.Contains(inhalt["testprojekt/deployment.md"], "freitags nie") {
t.Errorf("Eintrag fehlt im ZIP, enthalten: %v", schluessel(inhalt))
}
if _, ok := inhalt["testprojekt/_index.md"]; !ok {
t.Error("_index.md fehlt im ZIP")
}
// Der Download steht im Protokoll des Projekts.
ereignisse, _ := db.ListAudit(ctx, store.AuditFilter{ProjectID: projekt.ID})
gefunden := false
for _, e := range ereignisse {
if e.Action == store.ActionExportDownload && e.Actor == "verwalterin" {
gefunden = true
}
}
if !gefunden {
t.Error("Download fehlt im Protokoll")
}
}
// TestProjektDownloadNurFuerAdmins: Lesen und Schreiben reichen (vorerst)
// nicht, siehe Session.CanDownloadProject.
func TestProjektDownloadNurFuerAdmins(t *testing.T) {
srv, db, projekt := testServer(t)
h := testHandler(t, srv)
for _, rolle := range []auth.Role{auth.RoleRead, auth.RoleWrite} {
cookie := anmelden(t, h, erzeugeKey(t, srv, db, projekt.ID, "key-"+string(rolle), rolle))
if w := ladeHerunter(h, "/p/testprojekt/export.zip", cookie); w.Code != http.StatusForbidden {
t.Errorf("Rolle %s: Status %d, erwartet 403", rolle, w.Code)
}
}
}
// TestProjektDownloadFremdesProjekt: Der Admin von Projekt A kommt nicht an
// das ZIP von Projekt B.
func TestProjektDownloadFremdesProjekt(t *testing.T) {
srv, db, projekt := testServer(t)
h := testHandler(t, srv)
if _, err := db.CreateProject(context.Background(), projekt.WorkspaceID, "anderes", "Anderes", ""); err != nil {
t.Fatal(err)
}
cookie := anmelden(t, h, erzeugeKey(t, srv, db, projekt.ID, "verwalterin", auth.RoleAdmin))
if w := ladeHerunter(h, "/p/anderes/export.zip", cookie); w.Code == http.StatusOK {
t.Error("ZIP eines fremden Projekts ausgeliefert")
}
}
func TestWorkspaceDownload(t *testing.T) {
srv, db, projekt := testServer(t)
h := testHandler(t, srv)
ctx := context.Background()
anderes, err := db.CreateProject(ctx, projekt.WorkspaceID, "anderes", "Anderes", "")
if err != nil {
t.Fatal(err)
}
for _, p := range []store.Project{projekt, anderes} {
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 := anmeldenAlsWorkspaceAdmin(t, srv, h, projekt.WorkspaceID)
plattform := anmeldenAlsPlattformAdmin(t, h, projekt.WorkspaceID)
for name, cookie := range map[string]*http.Cookie{"Workspace-Admin": wsAdmin.cookie, "Plattform-Admin": plattform} {
w := ladeHerunter(h, "/admin/export.zip", cookie)
if w.Code != http.StatusOK {
t.Fatalf("%s: Status %d, erwartet 200", name, w.Code)
}
inhalt := zipInhalt(t, w.Body.Bytes())
for _, pfad := range []string{"_workspace.md", "testprojekt/notiz.md", "anderes/notiz.md"} {
if _, ok := inhalt[pfad]; !ok {
t.Errorf("%s: %s fehlt im ZIP, enthalten: %v", name, pfad, schluessel(inhalt))
}
}
}
// Ein Projekt-Admin verwaltet keine Projekte, also auch kein Workspace-ZIP.
key := anmelden(t, h, erzeugeKey(t, srv, db, projekt.ID, "verwalterin", auth.RoleAdmin))
if w := ladeHerunter(h, "/admin/export.zip", key); w.Code != http.StatusForbidden {
t.Errorf("Projekt-Admin: Status %d, erwartet 403", w.Code)
}
}
func TestDateinamenTeil(t *testing.T) {
faelle := map[string]string{
"Plattform": "plattform",
"Büro Süd & Nord": "buero-sued-nord",
`Böse "Name"` + "\r\n": "boese-name",
"!!!": "workspace",
"": "workspace",
}
for eingabe, erwartet := range faelle {
if got := dateinamenTeil(eingabe, "workspace"); got != erwartet {
t.Errorf("dateinamenTeil(%q) = %q, erwartet %q", eingabe, got, erwartet)
}
}
}
func schluessel(m map[string]string) []string {
var liste []string
for k := range m {
liste = append(liste, k)
}
return liste
}