Capture invite details before starting background email delivery
CI / test (push) Failing after 3m22s

This commit is contained in:
2026-09-15 20:19:22 +02:00
parent d06ff4a94a
commit df8544e04b
2 changed files with 105 additions and 2 deletions
+101
View File
@@ -0,0 +1,101 @@
package api
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"gardomatic.kleiax.de/internal/mailer"
"gardomatic.kleiax.de/internal/storage"
"github.com/julienschmidt/httprouter"
)
type gardenInviteTestModel struct {
invite storage.GardenInvite
}
func (m *gardenInviteTestModel) Upsert(invite storage.GardenInvite) (storage.GardenInvite, error) {
invite.ID = 23
invite.Token = "garden-invite-token"
m.invite = invite
return invite, nil
}
func (m *gardenInviteTestModel) GetByToken(string) (storage.GardenInvite, error) {
return storage.GardenInvite{}, storage.ErrRecordNotFound
}
func (m *gardenInviteTestModel) GetAllForGarden(int) ([]storage.GardenInvite, error) {
return nil, nil
}
func (m *gardenInviteTestModel) Delete(int, int) error { return nil }
func (m *gardenInviteTestModel) Accept(string, storage.User) (storage.GardenMember, error) {
return storage.GardenMember{}, storage.ErrRecordNotFound
}
type gardenInviteRoleTestModel struct{}
func (gardenInviteRoleTestModel) List(storage.RoleScope) ([]storage.Role, error) { return nil, nil }
func (gardenInviteRoleTestModel) ListForGarden(int) ([]storage.Role, error) { return nil, nil }
func (gardenInviteRoleTestModel) Get(string) (storage.Role, error) {
return storage.Role{}, storage.ErrRecordNotFound
}
func (gardenInviteRoleTestModel) GetForGarden(gardenID int, name string) (storage.Role, error) {
return storage.Role{Name: name, Scope: storage.RoleScopeGarden, GardenID: &gardenID}, nil
}
func (gardenInviteRoleTestModel) Create(role storage.Role) (storage.Role, error) { return role, nil }
func (gardenInviteRoleTestModel) Update(role storage.Role) (storage.Role, error) { return role, nil }
func (gardenInviteRoleTestModel) Delete(string) error { return nil }
func (gardenInviteRoleTestModel) ListGardenOverrides(int) ([]storage.GardenRolePermissionOverride, error) {
return nil, nil
}
func (gardenInviteRoleTestModel) ReplaceGardenOverrides(int, string, []storage.GardenRolePermissionOverride) error {
return nil
}
func TestCreateGardenInviteMailContainsToken(t *testing.T) {
invites := new(gardenInviteTestModel)
mailPath := filepath.Join(t.TempDir(), "mail.log")
configuredMailer, err := mailer.New(mailer.Config{Mode: mailer.ModeFile, Sender: "gardomatic@example.com", FilePath: mailPath})
if err != nil {
t.Fatal(err)
}
app, _, _ := newGardenTestApplication()
app.config.WebBaseURL = "https://garden.example.com/"
app.models.GardenInvites = invites
app.models.Roles = gardenInviteRoleTestModel{}
app.mailer = configuredMailer
request := httptest.NewRequest(http.MethodPost, "/v1/gardens/17/invites", strings.NewReader(`{"email":" ADA@EXAMPLE.COM ","role":"member"}`))
request = request.WithContext(context.WithValue(request.Context(), httprouter.ParamsKey, httprouter.Params{{Key: "gardenID", Value: "17"}}))
request = app.contextSetGardenMember(request, storage.GardenMember{GardenID: 17, UserID: 9, Role: storage.GardenRoleOwner})
response := httptest.NewRecorder()
app.createGardenInviteHandler(response, request)
app.wg.Wait()
if response.Code != http.StatusCreated {
t.Fatalf("status: got %d, want %d; body: %s", response.Code, http.StatusCreated, response.Body.String())
}
if strings.Contains(response.Body.String(), "garden-invite-token") {
t.Fatalf("API response exposes invitation token: %s", response.Body.String())
}
if invites.invite.Email != "ada@example.com" || invites.invite.ExpiresAt.Before(time.Now().Add(6*24*time.Hour)) {
t.Fatalf("unexpected stored invitation: %+v", invites.invite)
}
mailContent, err := os.ReadFile(mailPath)
if err != nil {
t.Fatal(err)
}
want := "token=3Dgarden-invite-token"
if !strings.Contains(string(mailContent), want) {
t.Errorf("invitation mail is missing %q: %s", want, mailContent)
}
}