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>
334 lines
10 KiB
Go
334 lines
10 KiB
Go
package store_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/netcell-it/deklarix/internal/store"
|
|
)
|
|
|
|
func TestAccountCRUD(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
acc, err := s.CreateAccount(ctx, store.AccountInput{Name: "Beispiel Agentur GmbH"})
|
|
if err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
|
|
got, err := s.GetAccount(ctx, acc.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetAccount: %v", err)
|
|
}
|
|
if got.Name != "Beispiel Agentur GmbH" {
|
|
t.Fatalf("Name = %q, want Beispiel Agentur GmbH", got.Name)
|
|
}
|
|
if got.EinladungToken == "" {
|
|
t.Error("expected a newly created account to have a non-empty EinladungToken")
|
|
}
|
|
}
|
|
|
|
func TestCreateAccountMitFirmendaten(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
acc, err := s.CreateAccount(ctx, store.AccountInput{
|
|
Name: "Vollstaendig GmbH", Strasse: "Musterstraße 1", PLZ: "12345", Ort: "Musterstadt",
|
|
Land: "Deutschland", UStID: "DE123456789", Rechnungsemail: "rechnung@vollstaendig.example.com",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
if acc.Strasse != "Musterstraße 1" || acc.PLZ != "12345" || acc.Ort != "Musterstadt" ||
|
|
acc.Land != "Deutschland" || acc.UStID != "DE123456789" || acc.Rechnungsemail != "rechnung@vollstaendig.example.com" {
|
|
t.Fatalf("Account = %+v, Firmendaten unvollständig gespeichert", acc)
|
|
}
|
|
|
|
got, err := s.GetAccount(ctx, acc.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetAccount: %v", err)
|
|
}
|
|
if got.Rechnungsemail != acc.Rechnungsemail {
|
|
t.Fatalf("Rechnungsemail nach GetAccount = %q, want %q", got.Rechnungsemail, acc.Rechnungsemail)
|
|
}
|
|
}
|
|
|
|
func TestUpdateAccountDetails(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
acc, err := s.CreateAccount(ctx, store.AccountInput{Name: "Alte Firma", Ort: "Altstadt"})
|
|
if err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
|
|
updated, err := s.UpdateAccountDetails(ctx, acc.ID, store.AccountInput{
|
|
Name: "Neue Firma", Strasse: "Neue Straße 2", PLZ: "54321", Ort: "Neustadt",
|
|
Land: "Österreich", UStID: "", Rechnungsemail: "buchhaltung@neue-firma.example.com",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdateAccountDetails: %v", err)
|
|
}
|
|
if updated.Name != "Neue Firma" || updated.Ort != "Neustadt" || updated.Land != "Österreich" {
|
|
t.Fatalf("Account nach Update = %+v, nicht wie erwartet aktualisiert", updated)
|
|
}
|
|
}
|
|
|
|
func TestUpdateAccountDetailsNotFound(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
_, err := s.UpdateAccountDetails(ctx, "00000000-0000-0000-0000-000000000000", store.AccountInput{Name: "X"})
|
|
if !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestUpdateAccount(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
acc, err := s.CreateAccount(ctx, store.AccountInput{Name: "Alter Name GmbH"})
|
|
if err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
|
|
updated, err := s.UpdateAccount(ctx, acc.ID, "Neuer Name GmbH")
|
|
if err != nil {
|
|
t.Fatalf("UpdateAccount: %v", err)
|
|
}
|
|
if updated.Name != "Neuer Name GmbH" {
|
|
t.Fatalf("Name = %q, want Neuer Name GmbH", updated.Name)
|
|
}
|
|
|
|
got, err := s.GetAccount(ctx, acc.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetAccount: %v", err)
|
|
}
|
|
if got.Name != "Neuer Name GmbH" {
|
|
t.Fatalf("Name nach erneutem Laden = %q, want Neuer Name GmbH", got.Name)
|
|
}
|
|
}
|
|
|
|
func TestUpdateAccountNotFound(t *testing.T) {
|
|
s := openTestStore(t)
|
|
_, err := s.UpdateAccount(context.Background(), "00000000-0000-0000-0000-000000000000", "X")
|
|
if !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err = %v, want store.ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestGetAccountByEinladungToken(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
acc, err := s.CreateAccount(ctx, store.AccountInput{Name: "Beispiel Agentur GmbH"})
|
|
if err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
|
|
got, err := s.GetAccountByEinladungToken(ctx, acc.EinladungToken)
|
|
if err != nil {
|
|
t.Fatalf("GetAccountByEinladungToken: %v", err)
|
|
}
|
|
if got.ID != acc.ID {
|
|
t.Fatalf("GetAccountByEinladungToken returned a different account")
|
|
}
|
|
|
|
_, err = s.GetAccountByEinladungToken(ctx, "unbekanntes-token")
|
|
if !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err = %v, want store.ErrNotFound for an unknown token", err)
|
|
}
|
|
}
|
|
|
|
func TestRegenerateEinladungToken(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
acc, err := s.CreateAccount(ctx, store.AccountInput{Name: "Beispiel Agentur GmbH"})
|
|
if err != nil {
|
|
t.Fatalf("CreateAccount: %v", err)
|
|
}
|
|
alterToken := acc.EinladungToken
|
|
|
|
if err := s.RegenerateEinladungToken(ctx, acc.ID, "ein-neues-token"); err != nil {
|
|
t.Fatalf("RegenerateEinladungToken: %v", err)
|
|
}
|
|
|
|
if _, err := s.GetAccountByEinladungToken(ctx, alterToken); !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("expected the old token to be invalid, got err = %v", err)
|
|
}
|
|
got, err := s.GetAccountByEinladungToken(ctx, "ein-neues-token")
|
|
if err != nil {
|
|
t.Fatalf("GetAccountByEinladungToken (neu): %v", err)
|
|
}
|
|
if got.ID != acc.ID {
|
|
t.Fatalf("GetAccountByEinladungToken (neu) returned a different account")
|
|
}
|
|
}
|
|
|
|
func TestUserCRUD(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
accID := testAccountID(t, s)
|
|
|
|
user, err := s.CreateUser(ctx, accID, "team@example.com", "bcrypt-hash", "mitarbeiter")
|
|
if err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
|
|
byEmail, err := s.GetUserByEmail(ctx, "team@example.com")
|
|
if err != nil {
|
|
t.Fatalf("GetUserByEmail: %v", err)
|
|
}
|
|
if byEmail.ID != user.ID {
|
|
t.Fatalf("GetUserByEmail returned a different user than CreateUser")
|
|
}
|
|
|
|
byID, err := s.GetUser(ctx, user.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetUser: %v", err)
|
|
}
|
|
if byID.Email != "team@example.com" || byID.AccountID != accID {
|
|
t.Fatalf("GetUser = %+v, unerwartete Werte", byID)
|
|
}
|
|
if !byID.Active {
|
|
t.Error("expected a newly created user to be Active by default")
|
|
}
|
|
}
|
|
|
|
func TestSetUserActive(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
accID := testAccountID(t, s)
|
|
user, err := s.CreateUser(ctx, accID, "deaktivierbar@example.com", "bcrypt-hash", "mitarbeiter")
|
|
if err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
|
|
if err := s.SetUserActive(ctx, user.ID, false); err != nil {
|
|
t.Fatalf("SetUserActive (false): %v", err)
|
|
}
|
|
deaktiviert, err := s.GetUser(ctx, user.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetUser: %v", err)
|
|
}
|
|
if deaktiviert.Active {
|
|
t.Fatal("expected the user to be inactive")
|
|
}
|
|
|
|
if err := s.SetUserActive(ctx, user.ID, true); err != nil {
|
|
t.Fatalf("SetUserActive (true): %v", err)
|
|
}
|
|
reaktiviert, err := s.GetUser(ctx, user.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetUser: %v", err)
|
|
}
|
|
if !reaktiviert.Active {
|
|
t.Fatal("expected the user to be active again")
|
|
}
|
|
}
|
|
|
|
func TestSetUserActiveNotFound(t *testing.T) {
|
|
s := openTestStore(t)
|
|
err := s.SetUserActive(context.Background(), "00000000-0000-0000-0000-000000000000", false)
|
|
if !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err = %v, want store.ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestGetUserByEmailNotFound(t *testing.T) {
|
|
s := openTestStore(t)
|
|
_, err := s.GetUserByEmail(context.Background(), "nichtvorhanden@example.com")
|
|
if !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err = %v, want store.ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestUserEmailIsUnique(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
accID := testAccountID(t, s)
|
|
|
|
if _, err := s.CreateUser(ctx, accID, "doppelt@example.com", "hash1", "mitarbeiter"); err != nil {
|
|
t.Fatalf("CreateUser (1): %v", err)
|
|
}
|
|
if _, err := s.CreateUser(ctx, accID, "doppelt@example.com", "hash2", "mitarbeiter"); err == nil {
|
|
t.Fatal("expected error for a duplicate email, got nil")
|
|
}
|
|
}
|
|
|
|
func TestSessionCRUD(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
accID := testAccountID(t, s)
|
|
|
|
user, err := s.CreateUser(ctx, accID, "session@example.com", "hash", "verantwortlicher")
|
|
if err != nil {
|
|
t.Fatalf("CreateUser: %v", err)
|
|
}
|
|
|
|
expiresAt := time.Now().Add(time.Hour).Truncate(time.Millisecond)
|
|
sess, err := s.CreateSession(ctx, "test-token-123", user.ID, expiresAt)
|
|
if err != nil {
|
|
t.Fatalf("CreateSession: %v", err)
|
|
}
|
|
|
|
got, err := s.GetSession(ctx, sess.Token)
|
|
if err != nil {
|
|
t.Fatalf("GetSession: %v", err)
|
|
}
|
|
if got.UserID != user.ID {
|
|
t.Fatalf("UserID = %q, want %q", got.UserID, user.ID)
|
|
}
|
|
if got.ImpersonatedByUserID != nil {
|
|
t.Errorf("ImpersonatedByUserID = %v, want nil for a regular session", got.ImpersonatedByUserID)
|
|
}
|
|
|
|
if err := s.DeleteSession(ctx, sess.Token); err != nil {
|
|
t.Fatalf("DeleteSession: %v", err)
|
|
}
|
|
if _, err := s.GetSession(ctx, sess.Token); !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err after delete = %v, want store.ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestGetSessionNotFound(t *testing.T) {
|
|
s := openTestStore(t)
|
|
_, err := s.GetSession(context.Background(), "unbekanntes-token")
|
|
if !errors.Is(err, store.ErrNotFound) {
|
|
t.Fatalf("err = %v, want store.ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateImpersonatedSession(t *testing.T) {
|
|
s := openTestStore(t)
|
|
ctx := context.Background()
|
|
kundenAcc := testAccountID(t, s)
|
|
kundenNutzer, err := s.CreateUser(ctx, kundenAcc, "kunde@example.com", "hash", "admin")
|
|
if err != nil {
|
|
t.Fatalf("CreateUser (Kunde): %v", err)
|
|
}
|
|
betreiberAcc := testAccountID(t, s)
|
|
betreiber, err := s.CreateUser(ctx, betreiberAcc, "betreiber@example.com", "hash", "betreiber")
|
|
if err != nil {
|
|
t.Fatalf("CreateUser (Betreiber): %v", err)
|
|
}
|
|
|
|
expiresAt := time.Now().Add(time.Hour).Truncate(time.Millisecond)
|
|
sess, err := s.CreateImpersonatedSession(ctx, "support-token-123", kundenNutzer.ID, betreiber.ID, expiresAt)
|
|
if err != nil {
|
|
t.Fatalf("CreateImpersonatedSession: %v", err)
|
|
}
|
|
if sess.UserID != kundenNutzer.ID {
|
|
t.Errorf("UserID = %q, want %q", sess.UserID, kundenNutzer.ID)
|
|
}
|
|
if sess.ImpersonatedByUserID == nil || *sess.ImpersonatedByUserID != betreiber.ID {
|
|
t.Fatalf("ImpersonatedByUserID = %v, want %q", sess.ImpersonatedByUserID, betreiber.ID)
|
|
}
|
|
|
|
got, err := s.GetSession(ctx, sess.Token)
|
|
if err != nil {
|
|
t.Fatalf("GetSession: %v", err)
|
|
}
|
|
if got.ImpersonatedByUserID == nil || *got.ImpersonatedByUserID != betreiber.ID {
|
|
t.Fatalf("GetSession ImpersonatedByUserID = %v, want %q", got.ImpersonatedByUserID, betreiber.ID)
|
|
}
|
|
}
|