Bei Firmenanlage (Registrierung + Betreiber-Firmenanlage) müssen jetzt Adresse (Straße, PLZ, Ort, Land) und Abrechnungsdaten (Rechnungsemail, optional USt-IdNr.) erfasst werden, nicht nur der Firmenname (Migration 0023). USt-IdNr. bewusst optional - Kleinunternehmer nach §19 UStG haben keine. Neue Seite /verwaltung/firma (admin-only) zum Einsehen/ Nachtragen für bestehende Firmen. store.CreateAccount nimmt jetzt ein AccountInput statt nur einen Namen entgegen (Signaturänderung betrifft ~20 Testaufrufe, mechanisch umgestellt). register.html/ betreiber_account_neu.html auf form-card/form-grid umgestellt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
189 lines
7.3 KiB
Go
189 lines
7.3 KiB
Go
package web_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/netcell-it/deklarix/internal/store"
|
|
)
|
|
|
|
func werkzeugForm() url.Values {
|
|
return url.Values{
|
|
"name": {"ChatGPT Enterprise"}, "anbieter": {"OpenAI"}, "verarbeitungslaender": {"Irland"},
|
|
"avv_verfuegbar": {"on"}, "avv_url": {"https://example.com/avv"},
|
|
"training_opt_out": {"on"}, "training_standard": {"on"}, "aufbewahrung_tage": {"30"},
|
|
"zertifizierungen": {"ISO 27001, SOC 2"}, "geeignete_zwecke": {"Textgenerierung"},
|
|
"einschraenkungen": {""}, "letzte_pruefung": {"2026-08-01"}, "quelle": {"https://example.com/beleg"},
|
|
}
|
|
}
|
|
|
|
func TestBetreiberCanCreateZentralesWerkzeug(t *testing.T) {
|
|
fs := newFakeStore()
|
|
s := newServer(t, fs)
|
|
betreiberCookie := seedAccountWithRole(t, fs, "Deklarix Betreiber", "betreiber@example.com", "betreiber")
|
|
|
|
resp := postForm(t, s, betreiberCookie, "/betreiber/werkzeuge", werkzeugForm())
|
|
if resp.Code != http.StatusSeeOther {
|
|
t.Fatalf("status = %d, want 303, body: %s", resp.Code, resp.Body.String())
|
|
}
|
|
|
|
var w store.Werkzeug
|
|
for _, wz := range fs.werkzeuge {
|
|
w = wz
|
|
}
|
|
if w.Name != "ChatGPT Enterprise" || w.AccountID != nil {
|
|
t.Fatalf("Werkzeug = %+v, unerwartete Werte", w)
|
|
}
|
|
if len(w.Zertifizierungen) != 2 {
|
|
t.Errorf("Zertifizierungen = %v, want 2 Einträge", w.Zertifizierungen)
|
|
}
|
|
|
|
list := getWithCookie(t, s, betreiberCookie, "/betreiber/werkzeuge")
|
|
if !strings.Contains(list.Body.String(), "ChatGPT Enterprise") {
|
|
t.Errorf("expected the new werkzeug in the Katalogliste, got: %s", list.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestBetreiberWerkzeugCreateRequiresLetztePruefungUndQuelle(t *testing.T) {
|
|
fs := newFakeStore()
|
|
s := newServer(t, fs)
|
|
betreiberCookie := seedAccountWithRole(t, fs, "Deklarix Betreiber", "betreiber@example.com", "betreiber")
|
|
|
|
form := werkzeugForm()
|
|
form.Set("letzte_pruefung", "")
|
|
resp := postForm(t, s, betreiberCookie, "/betreiber/werkzeuge", form)
|
|
if resp.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200 (Formular mit Fehler) ohne letzte_pruefung", resp.Code)
|
|
}
|
|
if len(fs.werkzeuge) != 0 {
|
|
t.Errorf("expected no werkzeug to be created, got %d", len(fs.werkzeuge))
|
|
}
|
|
}
|
|
|
|
func TestBetreiberCanUpdateAndDeleteZentralesWerkzeug(t *testing.T) {
|
|
fs := newFakeStore()
|
|
s := newServer(t, fs)
|
|
betreiberCookie := seedAccountWithRole(t, fs, "Deklarix Betreiber", "betreiber@example.com", "betreiber")
|
|
postForm(t, s, betreiberCookie, "/betreiber/werkzeuge", werkzeugForm())
|
|
var id string
|
|
for wid := range fs.werkzeuge {
|
|
id = wid
|
|
}
|
|
|
|
editResp := getWithCookie(t, s, betreiberCookie, "/betreiber/werkzeuge/"+id)
|
|
if editResp.Code != http.StatusOK || !strings.Contains(editResp.Body.String(), "ChatGPT Enterprise") {
|
|
t.Fatalf("edit form status = %d, body: %s", editResp.Code, editResp.Body.String())
|
|
}
|
|
|
|
updateForm := werkzeugForm()
|
|
updateForm.Set("name", "ChatGPT Enterprise (aktualisiert)")
|
|
updateResp := postForm(t, s, betreiberCookie, "/betreiber/werkzeuge/"+id, updateForm)
|
|
if updateResp.Code != http.StatusSeeOther {
|
|
t.Fatalf("update status = %d, body: %s", updateResp.Code, updateResp.Body.String())
|
|
}
|
|
if fs.werkzeuge[id].Name != "ChatGPT Enterprise (aktualisiert)" {
|
|
t.Errorf("Name = %q nach Update, unerwartet", fs.werkzeuge[id].Name)
|
|
}
|
|
|
|
deleteResp := postForm(t, s, betreiberCookie, "/betreiber/werkzeuge/"+id+"/loeschen", url.Values{})
|
|
if deleteResp.Code != http.StatusSeeOther {
|
|
t.Fatalf("delete status = %d, body: %s", deleteResp.Code, deleteResp.Body.String())
|
|
}
|
|
if _, ok := fs.werkzeuge[id]; ok {
|
|
t.Error("expected the werkzeug to be deleted")
|
|
}
|
|
}
|
|
|
|
// TestBetreiberCannotEditMandantenEigenesWerkzeug: ein mandantenspezifischer
|
|
// Katalogeintrag (account_id gesetzt) ist über die Betreiber-Seiten
|
|
// nicht erreichbar — das bleibt Sache des jeweiligen Mandanten.
|
|
func TestBetreiberCannotEditMandantenEigenesWerkzeug(t *testing.T) {
|
|
fs := newFakeStore()
|
|
s := newServer(t, fs)
|
|
betreiberCookie := seedAccountWithRole(t, fs, "Deklarix Betreiber", "betreiber@example.com", "betreiber")
|
|
acc, err := fs.CreateAccount(context.Background(), store.AccountInput{Name: "Mandant mit eigenem Werkzeug"})
|
|
if err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
eigenesID := "eigenes-werkzeug"
|
|
fs.werkzeuge[eigenesID] = store.Werkzeug{ID: eigenesID, AccountID: &acc.ID, Name: "Mandanten-eigenes Tool"}
|
|
|
|
resp := getWithCookie(t, s, betreiberCookie, "/betreiber/werkzeuge/"+eigenesID)
|
|
if resp.Code != http.StatusNotFound {
|
|
t.Fatalf("status = %d, want 404 for a mandanten-eigenes werkzeug", resp.Code)
|
|
}
|
|
}
|
|
|
|
func TestBetreiberWerkzeugListeFiltertNachSucheUndZweck(t *testing.T) {
|
|
fs := newFakeStore()
|
|
s := newServer(t, fs)
|
|
betreiberCookie := seedAccountWithRole(t, fs, "Deklarix Betreiber", "betreiber@example.com", "betreiber")
|
|
|
|
fs.werkzeuge["a"] = store.Werkzeug{
|
|
ID: "a", Name: "ChatGPT Enterprise", Anbieter: "OpenAI",
|
|
Verarbeitungslaender: []string{"USA"}, GeeigneteZwecke: []string{"Textgenerierung"},
|
|
LetztePruefung: time.Now(),
|
|
}
|
|
fs.werkzeuge["b"] = store.Werkzeug{
|
|
ID: "b", Name: "DeepL Pro", Anbieter: "DeepL SE",
|
|
Verarbeitungslaender: []string{"Deutschland"}, GeeigneteZwecke: []string{"Übersetzung"},
|
|
LetztePruefung: time.Now(),
|
|
}
|
|
|
|
resp := getWithCookie(t, s, betreiberCookie, "/betreiber/werkzeuge?q=deepl")
|
|
if resp.Code != http.StatusOK {
|
|
t.Fatalf("status = %d", resp.Code)
|
|
}
|
|
if strings.Contains(resp.Body.String(), "ChatGPT Enterprise") || !strings.Contains(resp.Body.String(), "DeepL Pro") {
|
|
t.Fatalf("q=deepl sollte nur DeepL Pro zeigen, body: %s", resp.Body.String())
|
|
}
|
|
|
|
resp = getWithCookie(t, s, betreiberCookie, "/betreiber/werkzeuge?zweck=Textgenerierung")
|
|
if !strings.Contains(resp.Body.String(), "ChatGPT Enterprise") || strings.Contains(resp.Body.String(), "DeepL Pro") {
|
|
t.Fatalf("zweck=Textgenerierung sollte nur ChatGPT Enterprise zeigen, body: %s", resp.Body.String())
|
|
}
|
|
|
|
resp = getWithCookie(t, s, betreiberCookie, "/betreiber/werkzeuge?q=nichts-passt")
|
|
if !strings.Contains(resp.Body.String(), "Keine Treffer") {
|
|
t.Fatalf("erwartet 'Keine Treffer' bei erfolgloser Suche, body: %s", resp.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestBetreiberWerkzeugListePaginiert(t *testing.T) {
|
|
fs := newFakeStore()
|
|
s := newServer(t, fs)
|
|
betreiberCookie := seedAccountWithRole(t, fs, "Deklarix Betreiber", "betreiber@example.com", "betreiber")
|
|
|
|
for i := 0; i < 25; i++ {
|
|
id := fmt.Sprintf("wz-%02d", i)
|
|
fs.werkzeuge[id] = store.Werkzeug{
|
|
ID: id, Name: fmt.Sprintf("Werkzeug %02d", i), Anbieter: "Testanbieter",
|
|
LetztePruefung: time.Now(),
|
|
}
|
|
}
|
|
|
|
resp := getWithCookie(t, s, betreiberCookie, "/betreiber/werkzeuge")
|
|
if resp.Code != http.StatusOK {
|
|
t.Fatalf("status = %d", resp.Code)
|
|
}
|
|
if !strings.Contains(resp.Body.String(), "Seite 1 von 2") {
|
|
t.Fatalf("erwartet 'Seite 1 von 2' bei 25 Einträgen (20 pro Seite), body: %s", resp.Body.String())
|
|
}
|
|
if !strings.Contains(resp.Body.String(), "Weiter") || strings.Contains(resp.Body.String(), "Zurück") {
|
|
t.Fatalf("Seite 1 sollte einen Weiter-, aber keinen Zurück-Button zeigen")
|
|
}
|
|
|
|
resp = getWithCookie(t, s, betreiberCookie, "/betreiber/werkzeuge?page=2")
|
|
if !strings.Contains(resp.Body.String(), "Seite 2 von 2") {
|
|
t.Fatalf("erwartet 'Seite 2 von 2', body: %s", resp.Body.String())
|
|
}
|
|
if strings.Contains(resp.Body.String(), "Weiter") || !strings.Contains(resp.Body.String(), "Zurück") {
|
|
t.Fatalf("Seite 2 sollte einen Zurück-, aber keinen Weiter-Button zeigen")
|
|
}
|
|
}
|