186 lines
5.7 KiB
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
|
|
}
|