package web_test import ( "bytes" "context" "mime/multipart" "net/http" "net/http/httptest" "strings" "testing" "github.com/netcell-it/deklarix/internal/web" ) // postCSV lädt csvContent als multipart-Formular mit Feldname "csv" hoch // — reale Browser-Uploads laufen genauso über multipart/form-data. func postCSV(t *testing.T, s *web.Server, cookie *http.Cookie, path, csvContent string) *httptest.ResponseRecorder { t.Helper() var body bytes.Buffer mw := multipart.NewWriter(&body) fw, err := mw.CreateFormFile("csv", "mitarbeiter.csv") if err != nil { t.Fatalf("CreateFormFile: %v", err) } if _, err := fw.Write([]byte(csvContent)); err != nil { t.Fatalf("Write: %v", err) } if err := mw.Close(); err != nil { t.Fatalf("mw.Close: %v", err) } req := httptest.NewRequest(http.MethodPost, path, &body) req.Header.Set("Content-Type", mw.FormDataContentType()) if cookie != nil { req.AddCookie(cookie) } w := httptest.NewRecorder() s.ServeHTTP(w, req) return w } func TestAdminCanImportMitarbeiterPerCSV(t *testing.T) { fs := newFakeStore() s := newServer(t, fs) adminCookie := seedAccountWithRole(t, fs, "Test-Mandant", "admin@example.com", "admin") resp := postCSV(t, s, adminCookie, "/verwaltung/nutzer/import", "email,role\nlisa@example.com,verantwortlicher\ntom@example.com,\n") if resp.Code != http.StatusOK { t.Fatalf("status = %d, body: %s", resp.Code, resp.Body.String()) } body := resp.Body.String() if !strings.Contains(body, "lisa@example.com") || !strings.Contains(body, "tom@example.com") { t.Fatalf("expected both imported emails in the result, got: %s", body) } lisa, err := fs.GetUserByEmail(context.Background(), "lisa@example.com") if err != nil { t.Fatalf("GetUserByEmail (lisa): %v", err) } if lisa.Role != "verantwortlicher" { t.Errorf("lisa.Role = %q, want verantwortlicher", lisa.Role) } tom, err := fs.GetUserByEmail(context.Background(), "tom@example.com") if err != nil { t.Fatalf("GetUserByEmail (tom): %v", err) } if tom.Role != "mitarbeiter" { t.Errorf("tom.Role = %q, want default mitarbeiter", tom.Role) } } func TestCSVImportZeigtEinmalpasswortDasFunktioniert(t *testing.T) { fs := newFakeStore() s := newServer(t, fs) adminCookie := seedAccountWithRole(t, fs, "Test-Mandant", "admin@example.com", "admin") resp := postCSV(t, s, adminCookie, "/verwaltung/nutzer/import", "email\nneu@example.com\n") if resp.Code != http.StatusOK { t.Fatalf("status = %d, body: %s", resp.Code, resp.Body.String()) } // Das angezeigte Einmalpasswort aus der -Zelle extrahieren. body := resp.Body.String() start := strings.Index(body, "") end := strings.Index(body, "") if start == -1 || end == -1 { t.Fatalf("expected a -Zelle mit dem Einmalpasswort, got: %s", body) } passwort := body[start+len("") : end] loginResp := postForm(t, s, nil, "/login", map[string][]string{ "email": {"neu@example.com"}, "password": {passwort}, }) if loginResp.Code != http.StatusSeeOther { t.Fatalf("login with the generated password: status = %d, body: %s", loginResp.Code, loginResp.Body.String()) } } func TestCSVImportMeldetUngueltigeRolleOhneDieAnderenZuBlockieren(t *testing.T) { fs := newFakeStore() s := newServer(t, fs) adminCookie := seedAccountWithRole(t, fs, "Test-Mandant", "admin@example.com", "admin") resp := postCSV(t, s, adminCookie, "/verwaltung/nutzer/import", "email,role\nboese@example.com,betreiber\ngut@example.com,mitarbeiter\n") if resp.Code != http.StatusOK { t.Fatalf("status = %d, body: %s", resp.Code, resp.Body.String()) } if _, err := fs.GetUserByEmail(context.Background(), "boese@example.com"); err == nil { t.Error("expected no user to be created for an invalid role (betreiber)") } if _, err := fs.GetUserByEmail(context.Background(), "gut@example.com"); err != nil { t.Error("expected the valid row to still be imported despite the earlier invalid row") } } func TestCSVImportOhneDateiZeigtFehler(t *testing.T) { fs := newFakeStore() s := newServer(t, fs) adminCookie := seedAccountWithRole(t, fs, "Test-Mandant", "admin@example.com", "admin") var body bytes.Buffer mw := multipart.NewWriter(&body) if err := mw.Close(); err != nil { t.Fatalf("mw.Close: %v", err) } req := httptest.NewRequest(http.MethodPost, "/verwaltung/nutzer/import", &body) req.Header.Set("Content-Type", mw.FormDataContentType()) req.AddCookie(adminCookie) w := httptest.NewRecorder() s.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status = %d, body: %s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "CSV-Datei auswählen") { t.Errorf("expected an error asking for a file, got: %s", w.Body.String()) } } func TestMitarbeiterCannotAccessCSVImport(t *testing.T) { fs := newFakeStore() s := newServer(t, fs) cookie := seedAccountWithRole(t, fs, "Test-Mandant", "mitarbeiter@example.com", "mitarbeiter") resp := getWithCookie(t, s, cookie, "/verwaltung/nutzer/import") if resp.Code != http.StatusNotFound { t.Fatalf("status = %d, want 404 for role mitarbeiter", resp.Code) } }