Capture invite details before starting background email delivery
CI / test (push) Failing after 3m22s
CI / test (push) Failing after 3m22s
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,9 +143,11 @@ func (app *application) createGardenInviteHandler(w http.ResponseWriter, r *http
|
|||||||
app.serverErrorResponse(w, r, err)
|
app.serverErrorResponse(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
inviteEmail := invite.Email
|
||||||
|
inviteURL := strings.TrimRight(app.config.WebBaseURL, "/") + "/invite?token=" + invite.Token
|
||||||
app.background(func() {
|
app.background(func() {
|
||||||
data := map[string]any{"inviteURL": strings.TrimRight(app.config.WebBaseURL, "/") + "/invite?token=" + invite.Token}
|
data := map[string]any{"inviteURL": inviteURL}
|
||||||
if sendErr := app.mailer.Send(invite.Email, "garden_invite.tmpl", data); sendErr != nil {
|
if sendErr := app.mailer.Send(inviteEmail, "garden_invite.tmpl", data); sendErr != nil {
|
||||||
app.logger.Error(sendErr.Error())
|
app.logger.Error(sendErr.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user